VPN 越しに DLNA 経由で NAS に触る (SSDP Proxy)

どうもみむらです。

VPN 越しに NAS のコンテンツに、しかも DLNA 対応アプリ経由で触りたいと思ったことはありませんか。

数ヶ月前に NAS を購入して、速攻で静音化の記事 ( ASUSTOR DataSync Center の HDD アクセスを静かにする ) を書いたりしていたのですが、DLNA 経由で音楽ファイルに触れるようにしてみた所これが大変使いやすく、ぜひ VPN 越しにもこれを享受したい・・と。。

DLNA は UPnP を用いており、その機器発見に SSDP (1900/udp) を用いているのですが、早い話が VPN 越しにブロードキャストが上手く動作しないので、これを改善してみようというそういう話です。


1.サーバから VPN 感を感じ取れないようにする

DLNA は同一ネットワーク上での動作を想定しているものになります。
他のネットワークからアクセスしようとすると上手く動いてくれないケースがいくつか見られましたので、下記のようにしてサーバからの 「VPN 感」を少し消してみます。

NAS 側には “192.168.1.0/24” のネットワークに属しているように見せ、VPN 用に “192.168.1.224/28” のネットワークを用意しておきます。

VPN サーバには Proxy ARP の設定を入れて、192.168.1.0/24 のネットワーク上からも 192.168.1.224/28 の端末の MAC アドレスの解決 (といっても VPN サーバが指し示されるだけ)が出来るようになります。

なお、Proxy ARP については、こちらのサイトが詳しいですので参照ください:
Proxy ARP(プロキシARP)とは / ネットワークエンジニアとして
https://www.infraexpert.com/study/gateway.htm


具体的な設定方法としては
“/proc/sys/net/ipv4/conf/<device>/proxy_arp” を 1 に書き換えるか
systemd-networkd の “[Network]” セクションに “IPv4ProxyARP=yes” とする方法が
簡単かなと思います。

設定する際は、“192.168.1.0/24” の方に ProxyARP の設定を適用する形になります。

systemd-networkd の設定例)

# cat /etc/systemd/network/eth0.network
[Match]
Name=eth0

[Network]
Address=192.168.1.240/24
Gateway=192.168.1.254
IPv4ProxyARP=yes

# cat /etc/systemd/network/eth1.network
[Match]
Name=eth1

[Network]
Address=192.168.1.238/28

[Link]
MTUBytes=1200

2. SSDP をプロキシする

探索プロトコルの通信を転送するようにしてみます。
転送しないと探索が VPN のネットワーク内で閉じてしまい、本来の機器まで届かなくなってしまいます。

SSDP の中身は概ね下記のような HTTP ベースの通信になっています。

# リクエストの例。(クライアントからサーバに対して送る)
# 239.255.255.250:1900/UDP に対して送出される
---
M-SEARCH * HTTP/1.1
MX: 5
ST: urn:schemas-upnp-org:device:MediaServer:1
MAN: "ssdp:discover"
User-Agent: UPnP/1.0
Host: 239.255.255.250:1900
Connection: close

---

# 応答例。
# サーバからリクエストを行ったクライアントのポートに対してユニキャストで応答が入る
---
HTTP/1.1 200 OK
Cache-Control: max-age=1800
EXT:
Location: http://192.168.1.22:55247/dms
Server: Linux 4.0.0 UPnP/1.0
ST: urn:schemas-upnp-org:device:MediaServer:1
USN: uuid:xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx::urn:schemas-upnp-org:device:MediaServer:1
Date: Sun, 16 May 2021 19:42:50 GMT

---

見ていただくとわかるように、応答の中にユニキャストアドレスが含まれているため、発見処理のみプロキシしてあげれば後は1対1での通信となります。

ということで下記のようなプログラムを書いてみました

#!/bin/python3

#
# SSDP (DLNA の探索プロトコル) を吸って吐くプロキシ。
# Wireguard のように純粋にbridgeできないネットワーク間で使うと幸せになれる気がします。
# (使ったことでネットワークがダウン等しても保証は出来ませんので、自己責任でどうぞ。)
#
# Author : Satoshi Mimura (@mimura1133)
#

import socket
from contextlib import closing

def main():
        multicast_group = '239.255.255.250'
        src_adapter_ip = '' # DLNA クライアントがいるネットワークの IP アドレスを指定
        dst_adapter_ip = '' # DLNA サーバがいるネットワークの IP アドレスを指定
        port = 1900
        timeout = 5.0

        with closing(socket.socket(socket.AF_INET,socket.SOCK_DGRAM)) as src_sock:
            src_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
            src_sock.bind(('',port))
            src_sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
                                  socket.inet_aton(multicast_group) + socket.inet_aton(src_adapter_ip))

            while True:
                request_data, client_addr = src_sock.recvfrom(4096)
                with closing(socket.socket(socket.AF_INET,socket.SOCK_DGRAM)) as dst_sock:
                    dst_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(dst_adapter_ip))
                    dst_sock.settimeout(timeout)
                    dst_sock.sendto(request_data,(multicast_group,port))
                    print("\033[31m [FORWARDED:REQUEST]\033[0m {} -> {}".format(client_addr,(multicast_group,port)))
                    print(request_data)
                    while True:
                        try:
                            response_data, server_addr = dst_sock.recvfrom(4096)
                            src_sock.sendto(response_data,client_addr)
                            print("\033[32m [FORWARDED:RESPONSE]\033[0m {} -> {}".format(server_addr,client_addr))
                            print(response_data)

                        except Exception:
                            break
        return

if __name__ == '__main__':
    main()

動かしてみた結果は下図の通り。中々上手く動いてます。

今回の検証には iPhone 側に OPlayer Lite を入れて検証しています。

3.永続化する

毎回手動で起動するのは筋がよくありませんので、systemd のサービスにしてしまいましょう。

まずは、先ほどの Proxy をするプログラムからデバッグ用の print 文を消去したものを準備します。

#!/bin/python3

#
# SSDP (DLNA の探索プロトコル) を吸って吐くプロキシ。
# Wireguard のように純粋にbridgeできないネットワーク間で使うと幸せになれる気がします。
# (使ったことでネットワークがダウン等しても保証は出来ませんので、自己責任でどうぞ。)
#
# Author : Satoshi Mimura (@mimura1133)
#

import socket
from contextlib import closing

def main():
        multicast_group = '239.255.255.250'
        src_adapter_ip = '' # DLNA クライアントがいるネットワークの IP アドレスを指定
        dst_adapter_ip = '' # DLNA サーバがいるネットワークの IP アドレスを指定
        port = 1900
        timeout = 5.0

        with closing(socket.socket(socket.AF_INET,socket.SOCK_DGRAM)) as src_sock:
            src_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
            src_sock.bind(('',port))
            src_sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
                                  socket.inet_aton(multicast_group) + socket.inet_aton(src_adapter_ip))

            while True:
                request_data, client_addr = src_sock.recvfrom(4096)
                with closing(socket.socket(socket.AF_INET,socket.SOCK_DGRAM)) as dst_sock:
                    dst_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(dst_adapter_ip))
                    dst_sock.settimeout(timeout)
                    dst_sock.sendto(request_data,(multicast_group,port))
                    while True:
                        try:
                            response_data, server_addr = dst_sock.recvfrom(4096)
                            src_sock.sendto(response_data,client_addr)

                        except Exception:
                            break
        return

if __name__ == '__main__':
    main()

上記を /usr/local/sbin/proxy_dlna.py として保存しておきます。

保存が完了したら下記のように systemd のサービスを作成します。
今回は下記の内容を /etc/systemd/system/proxy_dlna.service として作成しました。

[Unit]
Description=Launch DLNA Proxy (/usr/local/sbin/proxy_dlna.py)
# After=wg-quick@wg0.service  #WireGuard を使っている場合はこうしておくと WireGuard が起動した後に起動するようになります。

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/proxy_dlna.py

[Install]
WantedBy=multi-user.target

保存が完了しましたら systemctl enable proxy_dlna 等で有効にすれば完了です。


手順は以上となります。

ただ上記のようなプロキシで全て動作するかというとそうではなく、特に DTCP-IP 等を用いるシステムの場合は「TTL は 3ホップ以下」「RTT は 7ms 以下」という制約が存在するため実現するのが大変難しい状況になっています。

もしそのような場合については NTT の NGN 網の特殊性を用いて回避しているケースや、L2TPv3 などを上手く組み合わせて回避しているケースがネット上にいくつか転がっていますのでそちらを用いるのが良いかなと思われます。

とはいえ、当方環境ではこれで VPN ライフがかなり改善されましたので、もしよろしければ参考にしていただけたらと!

SARA の更新はじめます。

どうも、みむらです。

サポート掲示板でよく「擬似言語シミュレータ SARA」のお問い合わせを受けるのですが、
ソースコードの欠損やバージョンの差異などによりサポートが難しい状況でした。

今回、一部データの補足を加えて Visual Studio 2019 にてビルドが出来る状況になりましたので、リファクタリングと機能追加含めてはじめていこうと考えています。
(もう Windows Forms でなくて XAML で良いよね、とか、 .NET Framework 4.8 ベースとかにしても良いよね・・とか色々と。。)

にしても最初のリリースは10年前だったんですね。。。
https://xtech.nikkei.com/it/article/NEWS/20101004/352568/

ということで、ボチボチはじめていきますー。

ASUSTOR DataSync Center の HDD アクセスを静かにする

どうもみむらです。

時代の流れに沿って(?)最近 NAS を買ってみました。

録画データを置いておいたり、時々使う ISO ファイルの保存場所に使ってみたり DLNA で色んな機器から触れるようになって QoL があがったりと「NAS は良いものだ」なんて感じている今日この頃です。

DataSync Center というソフトを入れると、外部のクラウドサービスとも同期してくれるので、スマホで撮った画像をクラウド経由で NAS に入れて DLNA で触れるようになったり・・等々、とても QoL が上がっております。

ただそんな NAS と DataSync Center なのですが、このソフトが常時 HDD にアクセスに行くために夜中も音がしてしまい、これは精神衛生上よろしくないので静音化を図ることにしてみました。


内容について

紹介する内容を実施した後で不具合が発生しても、私自身は責任を負えないため自己責任でお願い致します。

下記の内容としては稼働中の datacenter のメタデータを tmpfs に配置して解決というものになります。後半に作成物がありますので、お急ぎの方はそちらを参照ください。


1. 原因を追う

1.1. entware のインストールと SSH アクセスの許可

DataSync Center が何にアクセスしているかをトレースするため
entware のインストールと、SSH アクセスの許可を行います。

App Central から entware-ng インストール

以上の手順を踏むと “/opt/bin/opkg” から entware のパッケージマネージャが使えるようになります。


1.2. strace で挙動を見る

sudo su 等で root に登った後、下記のコマンドでインストールします。

なお opkg のパッケージは基本的に /opt 以下で触れるように配置されます。
またそれらのデータ群は /usr/local/AppCentral/entware-ng/opt/ 以下にシンボリックリンクが張られるようになるため、App Central から entware を削除すると、同時に opkg で入れたファイル群も削除されるようです。

# /opt/bin/opkg update
Downloading http://bin.entware.net/armv7sf-k2.6/Packages.gz
Updated list of available packages in /opt/var/opkg-lists/entware

# /opt/bin/opkg install strace
Installing strace (5.10-1) to root...
Downloading http://bin.entware.net/armv7sf-k2.6/strace_5.10-1_armv7-2.6.ipk
Configuring strace.

次に ps コマンドでプロセスを探します

# ps | grep datasync
 3442 root       0:00 grep datasync
 5145 ***** 1295:0 /usr/local/AppCentral/datasync-center/bin/datasync_center 1

上記の例では PID は 5145 になっていることが分かります。
では strace でアタッチして挙動を観察しましょう。

# /opt/bin/strace -p (PID)
(上記例の場合は /opt/bin/strace -p 5145 と入力する)

gettimeofday({tv_sec=1619546282, tv_usec=544859}, NULL) = 0
gettimeofday({tv_sec=1619546282, tv_usec=544976}, NULL) = 0
gettimeofday({tv_sec=1619546282, tv_usec=545057}, NULL) = 0
stat64("/usr/builtin/etc/ldapclient/asldapclient.conf", 0xbead4188) = -1 EACCES (Permission denied)
stat64("/etc/passwd", {st_mode=S_IFREG|0644, st_size=461, ...}) = 0
open("/etc/passwd", O_RDONLY|O_LARGEFILE) = 12
read(12, "root:x:0:0:,0,,root:/root:/bin/s"..., 461) = 461
close(12)                               = 0
stat64("/etc/passwd", {st_mode=S_IFREG|0644, st_size=461, ...}) = 0
open("/etc/passwd", O_RDONLY|O_LARGEFILE) = 12
read(12, "root:x:0:0:,0,,root:/root:/bin/s"..., 461) = 461
close(12)                               = 0
stat64("/etc/passwd", {st_mode=S_IFREG|0644, st_size=461, ...}) = 0
open("/etc/passwd", O_RDONLY|O_LARGEFILE) = 12
read(12, "root:x:0:0:,0,,root:/root:/bin/s"..., 461) = 461
close(12)                               = 0
stat64("/etc/nas.conf", {st_mode=S_IFREG|0644, st_size=3257, ...}) = 0
open("/etc/nas.conf", O_RDONLY|O_LARGEFILE) = 12
read(12, "[Basic]\nModel = AS1002T\nVersion "..., 3257) = 3257
close(12)                               = 0
lstat64("/share", {st_mode=S_IFDIR|0755, st_size=1024, ...}) = 0
lstat64("/share/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/share/home/*******", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
open("/share/home/*******/.datasync-center/connections/1/user_info.json.lock", O_RDWR|O_CREAT|O_LARGEFILE|O_CLOEXEC, 0777) = 12
flock(12, LOCK_EX)                      = 0
open("/share/home/*******/.datasync-center/connections/1/user_info.json_5145.tmp", O_WRONLY|O_CREAT|O_TRUNC, 0644) = 15
write(15, "{\"quota\":1131723882496,\"normal\":"..., 109) = 109
close(15)                               = 0
rename("/share/home/*******/.datasync-center/connections/1/user_info.json_5145.tmp", "/share/home/*******/.datasync-center/connections/1/user_info.json") = 0
open("/etc/passwd", O_RDONLY|O_CLOEXEC) = 15
_llseek(15, 0, [0], SEEK_CUR)           = 0
fstat64(15, {st_mode=S_IFREG|0644, st_size=461, ...}) = 0
mmap2(NULL, 461, PROT_READ, MAP_SHARED, 15, 0) = 0xb6faa000
_llseek(15, 461, [461], SEEK_SET)       = 0
munmap(0xb6faa000, 461)                 = 0
close(15)                               = 0
geteuid32()                             = 1000
chmod("/share/home/*******/.datasync-center/connections/1/user_info.json", 0700) = 0
flock(12, LOCK_UN)                      = 0
close(12)                               = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({tv_sec=1, tv_nsec=0}, 

定期的な read は良いとしても 「user_info.json_5145.tmp という名前でファイルを書いて、 user_info.json を消して、user_info.json に置換する」というのを定期的に行われるのは、やはりあんまり心臓にもよろしくないですね。。

ということで、これらを tmpfs に配置するようにしてみます。
(ディスクの書き込み先を RAM 上に作成したドライブに変更して、ディスクへの書き込みを行わせない)


2. データを tmpfs に配置するようにする。

2.1. 現在のデータをコピーして取っておく

方針としては、起動時・シャットダウン時にメタデータをコピーしておき
稼働中は tmpfs に対してずっと書かせるようにしてみます。

/share/home/(USERNAME)/.datasync-center/connections 以下のデータがずっと読み書きされていますので、connections 以下を tmpfs にするように丸っとコピーを取ります。

# /volume1/.@plugins/etc/init.d/K50datasync-center start
(DataSync Center を停止する)

# cd /share/home/(USERNAME)/.datasync-center/
( ユーザ名が hogeo であれば cd /share/home/hogeo/.datasync-center/ になる.)

# ln -ln
(UID,GID を調べておく. 下記の例では UID=1000, GID=100)
drwxrwxrwt    3 1000     100             60 Apr 23 11:55 connections

# cp -R connections original_connections
(コピーを取る)

# cd original_connections
# find -name "*lock" | xargs rm
# find -name "pid" | xargs rm
# find -name "sock" | xargs rm
(最後3つは誤作動の原因になりそうなものの削除です)

2.2. 起動時・シャットダウン時の挙動をサービスとして記述する

下記のサイトを参考にすると “/volume1/.@plugins/etc/init.d/ ” あたりにスクリプトを配置すると幸せになれそうですので、ここにスクリプトを記述していきます。

Digging in to the Linux install on a NAS
https://ljones.dev/blog/digging-into-nas/

基本的には下記2つのスクリプトを作成するだけです。


/volume1/.@plugins/etc/init.d/S01prepare-datasync-center

このスクリプトでは既存の connections ディレクトリを削除して tmpfs (かつ UID, GID はユーザに合わせる) をそこにマウントさせた後、コピーしておいたものを書き戻す処理になります。

下記スクリプトを利用される際は (USERNAME) 部分および (UID) 部分をご自身の環境に合わせて書き換えてください。

#!/bin/sh -e

rm -rf /share/home/(USERNAME)/.datasync-center/connections
mkdir /share/home/(USERNAME)/.datasync-center/connections
mount tmpfs -t tmpfs /share/home/(USERNAME)/.datasync-center/connections -o uid=(UID),gid=100

cp -R /share/home/(USERNAME)/.datasync-center/original_connections/* /share/home/(USERNAME)/.datasync-center/connections/
chown -R (UID):100 /share/home/(USERNAME)/.datasync-center/connections/

/volume1/.@plugins/etc/init.d/K99store-datasync-center

このスクリプトでは既存の connections ディレクトリの中身を待避させ、かつ誤作動の原因になりそうなものを削除する内容になります。

上記同様、下記スクリプトを利用される際は (USERNAME) 部分および (UID) 部分をご自身の環境に合わせて書き換えてください。

#!/bin/sh -e

cp -R /share/home/(USERNAME)/.datasync-center/connections/* /share/home/(USERNAME)/.datasync-center/original_connections/
find /share/home/(USERNAME)/.datasync-center/original_connections/ -name "*lock" | xargs rm
find /share/home/(USERNAME)/.datasync-center/original_connections/ -name "pid" | xargs rm
find /share/home/(USERNAME)/.datasync-center/original_connections/ -name "sock" | xargs rm

上記二つのスクリプトを書き上げた後は chmod で実行権限を与えることを忘れずに。

# vi /volume1/.@plugins/etc/init.d/S01prepare-datasync-center
( スクリプトを書く。書き上がったら :wq で抜ける )
# vi /volume1/.@plugins/etc/init.d/K99store-datasync-center
( スクリプトを書く。書き上がったら :wq で抜ける )
# chmod 755 /volume1/.@plugins/etc/init.d/S01prepare-datasync-center
# chmod 755 /volume1/.@plugins/etc/init.d/K99store-datasync-center

2.3. NAS を再起動して動作することを確認する

再起動後に root で mount コマンドを実行して tmpfs がマウントされていることを確認します。

# mount
rootfs on / type rootfs (rw)
proc on /proc type proc (rw,relatime)
sysfs on /sys type sysfs (rw,relatime)
devpts on /dev/pts type devpts (rw,relatime,gid=5,mode=620,ptmxmode=000)
tmpfs on /tmp type tmpfs (rw,relatime)
/dev/md0 on /volume0 type ext4 (rw,relatime,data=ordered)
/dev/loop0 on /share type ext4 (rw,relatime)
/dev/md1 on /volume1 type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/home type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Web type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Music type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Photos type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Pictures type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Movie type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/MSDN type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Media type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
tmpfs on /share/home/******/.datasync-center/connections type tmpfs (rw,relatime,uid=****,gid=100)

また DataSync Center を開き、正常に動作していることを併せて確認します。


3. 後片付け

App Central から entware-ng の削除 (インストールした場合) と、

SSH の無効化を忘れずに行っておきましょう。


ということで・・。

ここまで完了すると、HDD への DataSync Center による常時アクセスが無くなるため、 “Disk Hibernation” の設定に従って、未使用時間は眠ってくれるようになります。

快適な睡眠も得られて、めでたしめでたし・・です!
(書いてる時間的に寝られているのか、というのは一端脇に置いておきましょう)

素敵な NAS ライフをー!

コンソールで Optane Memory を有効にする

あけましておめでとうございます。
今年も宜しくお願い致します。

自宅内で Windows Server 2019 が走っているのですが、
大きなデータを保存しておく場所として HDD が搭載されており
これの高速化として “Optane Memory” を使おうとしてみました。

入れてみて、さて有効にしようと画面を開こうとしたところ・・

The Intel Optane memory application ran into a problem と表示される

ということで、起動が出来ません。でも Optane Memory での高速化はしたい。

ただコンソールで Optane Memory の有効化操作を行ったところ
上手く有効になりましたので、今後のメモとしてここに記録します。


記事にあたり:

インテル公式としては、 Windows Server での Optane Memory の動作は保証していないようです。
本番環境に導入するなどして障害が起きてもこちらではカバー出来ませんので、あくまでも実験用やデータが飛んでも問題ない環境でお試しください。

インテル® Optane™メモリー: 購入する前に、主な要件https://www.intel.co.jp/content/www/jp/ja/support/articles/000023994/memory-and-storage/intel-optane-memory.html


1.ドライバを入れる

セットアッププログラムを使用したり、
pnputil コマンド ( 例 : pnputil /add-driver iaStorAC.inf /install ) などでドライバをインストールします。

18.x 系のドライバが必要なため、マザーボードベンダなどで提供されているドライバが古い場合は最新版のドライバを入れておけば大丈夫だと思います。

インテル® Optane™・メモリーを備えたインテル®ラピッド・ストレージ・テクノロジードライバー・インストール・ソフトウェア
https://downloadcenter.intel.com/ja/download/29978

また、ドライバが上手く当たらない場合 (古いバージョンのまま、等) は
devcon.exe を Windows Driver Kit (WDK) から持ってきて更新することも可能です。

DevCon Update – Windows drivers | Microsoft Docs
https://docs.microsoft.com/ja-jp/windows-hardware/drivers/devtest/devcon-update


2. RSTCliPro を用意する

下記のサイトから RSTCliPro.exe をダウンロードし、
当該端末 (今回の私の場合は Windows Server 2019) にコピーしておきます。

インテル® Optane™メモリー向けインテル® RSTCLI Pro
https://downloadcenter.intel.com/ja/download/29986/-Optane-RSTCLI-Pro


3. Optane Memory と 高速化したい HDD の ID を確認する

下記のコマンドを実行します

RstCliPro.exe -I

実行すると下記のようにデバイス一覧が出てきます

そのうち、高速化したいデバイスの ID と Optane Memory の ID を探して控えます。
今回の場合は “0-0-5-0” が高速化したいデバイスで、 “0-1-0-0” が Optane Memory の ID になります。


4. Optane Memory を有効化する

先ほどメモをした ID を元に次のようなコマンドを実行します。

RstCliPro --OptaneMemory --enable --fast-drive <Optane MemoryのID> --drive-to-accel <高速化したいドライブ>

私の場合は、
“RstCliPro.exe –OptaneMemory –enable –fast-drive 0-1-0-0 –drive-to-accel 0-0-5-0”
というコマンドになります。

実行して “Enable completed” と出れば OK です。


以上で設定は完了です。

上記が完了した後 “RstCliPro.exe –OptaneMemory –info” を実行することで設定内容を確認することも可能です。

というわけで、素敵な Optane Memory ライフを!

Writeup : 2020年に修正されたとある脆弱性について

どうもみむらです。

年末です。後数時間で年が変わります。
私はといえば、「千葉県少年少女オーケストラとアキラさんの大発見コンサート2020」の放送を見ながら、この記事を書き終えたらソバをと最後の力をふりしぼっているところです。

宮川彬良さんのアルバム、時々仕事中にも聞いておりますが
音楽の中に遊びというか楽しみポイントが多数含まれているのもあって、すごい人だなと思うわけです。

・・・いや、今回はそういうことを書きたいわけではなくて。


今年、9月頃にとある一つの脆脆弱性が修正されました。

私が報告者になっているもので古いバージョンの製品に含まれていたものではありますが、
色々なベンダーさんでもやってしまいそうな知見が多々ありましたので
軽くご紹介できたらな、と。

なお、仕事柄脆弱性診断は行っておりますが、
趣味の範囲での発見で既に修正されているものになります。

またこの記事は特定の製品やブランドを貶めることは一切目的としておりません
自社の製品に対して「こういう脆弱性あるかも知れない」という気づきとして使って頂くことを目的としておりますので、ご理解ください。


今回の脆弱性では
「ソフトウェアアップデート」の処理において下記のような問題点がありました。

  • ソフトウェアアップデートのファイルの検証に不備があり
    任意のファイルを挿入できた。
  • ソフトウェアアップデートのファイルの処理に不備があり
    高権限で任意のコードを実行できた。

1.ソフトウェアアップデートのファイル検証

今回のソフトウェアではアップデートの方式が下記のような仕組みになっていました:

  1. サーバからアップデートのメタ情報を取得する
  2. メタ情報に手元のソフトウェアより新しいソフトウェアがあればダウンロードする
  3. アップデートに際し追加の処理が必要な場合は実行する

今回一つ目の問題点だったのは
「ソフトウェアアップデートのファイル検証」が不十分だったことでした。

ダウンロードしたファイルに付属しているメタデータは下記のような構造でした。
(実際にはもっと多くの情報などがあり、このように簡易な形にはなっていません)

ここにおいてのファイルチェックの方法に下記の不備がありました。
(繰り返しではありますが既に修正済みです)

  • “file” 要素で記述されているファイルのみチェックされる
    記述されていないファイルはチェックされない
  • “file” 要素で書かれている内容が MD5 や SHA1によるファイルのハッシュのみだった。
    → ファイルの整合性は検証できるが、
     「信頼出来るところからのものか」を検証できない

今回のケースでは、その先の処理でファイルの信頼性の検証は行われていましたが
ソフトウェアアップデートの処理を作成する際に上記のような項目のテストは
抜けがちになるのではないかと感じています。

対策としては下記のような追加の検証処理の追加や、
テストコードの追加が出来るとよいかと思います。

  1. MD5 や SHA1 などの整合性チェックに加えて
    電子署名を用いたアップデートファイルの信頼性の検証を付加する
  2. アップデートファイルが zip や tar といった一般的なアーカイブファイルを併用している場合などは、メタ情報などに記述されていないファイルが入っていた場合に検証を失敗させるなどする。

1に関しては例えば OpenSSL を用いて下記のような処理で実現できます:

# ベンダーが持つ秘密鍵で電子署名を作成する
$ openssl dgst -sha256 -sign private-key.pem patch.zip > patch.sig

# 受け取り側では公開鍵と提供された署名 (patch.sig) を用いて検証する
$ openssl dgst -sha256 -verify public-key.pem -signature patch.sig patch.zip

C# ですと “RSAPKCS1SignatureDeformatter.VerifySignature” などを用いて
実現するのも良いと思います。

参考例 / 引用元 : デジタル署名を作成、検証する – .NET Tips (VB.NET,C#…)
https://dobon.net/vb/dotnet/string/digitalsignature.html
( 一部コードの修正などを筆者側で加えています )

using System;
using System.Text;
using System.Security.Cryptography;

/// <summary>
/// デジタル署名を作成する
/// </summary>
/// <param name="message">署名を付けるメッセージ</param>
/// <param name="privateKey">署名に使用する秘密鍵</param>
/// <returns>作成された署名</returns>
public static string CreateDigitalSignature(
    string message, string privateKey)
{
    //メッセージをバイト型配列にして、SHA1ハッシュ値を計算
    var msgData = Encoding.UTF8.GetBytes(message);
    var hashData = (new SHA1Managed()).ComputeHash(msgData);

    //RSACryptoServiceProviderオブジェクトの作成
    var rsa = new RSACryptoServiceProvider();
    //秘密鍵を使って初期化
    rsa.FromXmlString(privateKey);

    //RSAPKCS1SignatureFormatterオブジェクトを作成
    var rsaFormatter = new RSAPKCS1SignatureFormatter(rsa);
    //署名の作成に使用するハッシュアルゴリズムを指定
    rsaFormatter.SetHashAlgorithm("SHA1");

    //署名を作成し、文字列に変換して返す
    return Convert.ToBase64String(rsaFormatter.CreateSignature(hashData));
}

/// <summary>
/// デジタル署名を検証する
/// </summary>
/// <param name="message">署名の付いたメッセージ</param>
/// <param name="signature">署名</param>
/// <param name="publicKey">送信者の公開鍵</param>
/// <returns>認証に成功した時はTrue。失敗した時はFalse。</returns>
public static bool VerifyDigitalSignature(
    string message, string signature, string publicKey)
{
    var msgData = Encoding.UTF8.GetBytes(message);
    var hashData = (new SHA1Managed()).ComputeHash(msgData);

    //RSACryptoServiceProviderオブジェクトの作成
    var rsa = new RSACryptoServiceProvider();
    //公開鍵を使って初期化
    rsa.FromXmlString(publicKey);

    var rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
    //署名の検証に使用するハッシュアルゴリズムを指定
    rsaDeformatter.SetHashAlgorithm("SHA1");

    //署名を検証し、結果を返す
    return rsaDeformatter.VerifySignature(hashData,
        Convert.FromBase64String(signature));
}

余談ですが、今回脆弱性を発見するきっかけになったのは
「メタ情報」に容易にアクセスできたことでした。

今回、ソフトウェアが Windows の Proxy 設定をアップデート時に使用するようになっており、プロキシを挟んだ途端に上記のように通信が確認出来る状態になりました。

Windows では Proxy 設定は、グループポリシ等で制約している場合を除き、

KEY : HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings
Element: ProxyServer 
Type : REG_SZ

またこのとき、アップデートの通信に関し HTTPS 通信で行われていましたが、
HTTPS 通信の証明書の検証は行われておらず、改竄した内容がそのまま信頼される状態になっていました。

HTTPS 通信の証明書の検証は、一般的に検証をした方が不必要なリスクから守ることができる場合が多いため行った方が良いです。

ただ今回のような「アップデータ」の場合は、検証に失敗したことによってアップデートが出来なくなるという可能性も考えられますので、どちらのリスクを取るかは検討した方が良いかと思いますし、先述のファイルに対する電子署名を信頼して、HTTPS 通信の証明書の検証はしないという選択もありだと思います。


また、今回の脆弱性を外部から悪用する場合、DNS の変更か Proxy の変更が必要でした。
(その他の方法もありますが割愛します)

世の中を見渡してみますと「インターネットの高速化」などを偽って、ローカルの DNS 設定を書き換えてしまうマルウェア ( 例:ユーザーに気付かれずにひそかにDNS設定を変えてしまう「DNSアンロッカー」 / マルウェア情報局 ) や 脆弱なルータを突いて設定を書き換えてしまう攻撃 (例: ルーターをハッキングし、利用者をフィッシングサイトへリダイレクトする攻撃 / カスペルスキー公式ブログ ) も出ている背景がありますので、
必ずしもかなり難しい攻撃ではないと考えられます。

ですので、DNS や HTTPS の証明書といった経路に対して信頼を付与するのではなく、通信内容(今回の場合はアップデートのファイル)について「エンド-エンド」での信頼を前提として、その上で HTTPS の証明書検証をするといったような形がよいと考えます。


2.ソフトウェアアップデート処理が高権限で走っている

アップデートの処理がコンピュータ内で一番高い権限を持っているプロセスで実行されており、アップデート処理を細工することで任意のプログラムを一番高い権限で実行できるようになっていました。

アップデートファイルの展開処理などが高い権限で実行されている場合
アーカイブファイルの細工 (例 : Zip Slip / ディレクトリトラバーサルが発生する zip ファイルを用いて、意図しないファイルの書き換えを行えてしまう ) をはじめとした色々な要因によってファイルの意図しない変更が発生する可能性があります。

対策としては、ファイルの検証は低い権限で行うなど、不必要に高い権限を与えないように設計するのが良いかと思います。

また、今回の例ではありませんでしたが
アップデートの処理が書かれたプログラム(アップデータ)を配布ファイルに含めている実装もいくつか見られます。

この場合、可能であれば既に稼働中のプログラムに処理を入れ(ファイルの書き換えを稼働中の信頼出来るプログラムによって行うようにし)権限を徐々に与えるという形が良いでしょう。


そばが食べたい一心で書いていましたので、
途中誤字や設計としてそれはオカシイ!という点もあるかも知れません。
指摘等は受け付けておりますので、気兼ねなくメッセージをいただければ幸いです。


余談ではありますが、「脆弱性情報」と聞くとその製品を叩いたり「こんな危険なものは使えない!」と思われる方がいらっしゃると思うのですが、それは違うと思っています。

逆に「実はあるのに、みんな黙っている世界」というのを考えてみたら、そっちの方が怖いかと思いますし、それでしたら「どういうことがあって狙われる、こういう風にすることで修正できる」という話を優しく広げていくことが大事なのではないかなと。

私自身、元々ソフトウェアディベロッパーから、今は情報セキュリティ関係を主軸にしていますが、正直「情報セキュリティの人たちすごく怖いな・・」というのはかつてありまして、今も時々感じることはあります。

「これが分かってて当然」「これが出来ないのはオカシイ」という話がよく出てくる世の中ではありますが、失敗しても治していくことができればそれでいいんじゃないかな、とかつての経験とかを振り返りながら思う今日この頃です。
(何かありましたら気兼ねなく聞いてもらえればと!)

それでは、よいお年をお迎えください!