Windows」カテゴリーアーカイブ

ExQueueWorkItem を使って PASSIVE_LEVEL で呼び出せる命令を他の IRQL から実行させる。

どうもみむらです。

 

最近とある用があってカーネルモードドライバを書くことが出てきました。
なんだかんだ、Linux 用のドライバも Windows 用のドライバも慣れると楽しいですね。

(ブルースクリーンに行かないように考えながらコードを書くのが良い感じに脳みその体操になってとっても楽しいです。 気を抜くとすぐにブルースクリーンになりますし。)

 

さてさて。

Windows なカーネルモードドライバを書いている上でなかなかに問題になることの一つに
IRQL の話があると思います。

詳しくはこちらのウェブサイトに詳しくまとめられていますのでそちらにお任せするとして。
http://d241445.hosting-sv.jp/community/report/report11.html

要は各割り込みに優先度を付けて、すぐに行わなければならないやつをすぐに行えるようにする・・と
おおざっぱですがそのような感じです。

 

また各 IRQL によって実行可能な API が限られており、
もしそれを無視して実行すると

image

こんな感じで、 “IRQL_NOT_LESS_OR_EQUAL” な BSoD が発生します。
(ちなみにこれは KeBugCheckEx に 0xA と適当(?)な数字を設定して呼び出しました。)

 

各種コールバックでは DISPATCH_LEVEL でやってくるケースもあり、
PASSIVE_LEVEL でのみ呼び出せる関数をどうやって呼び出すか。


今回の一件は、
フィルタを挟み込んでデータをファイルに書き出すというようなドライバを書いていて、
どこかのタイミングで書き出せればいいと考えて、

ExQueueWorkItem を DelayedWorkQueue で呼び出して、
呼び出し先にファイル書き込みを書く・・という方法で対処しました。

 

コードとしてはこんな感じ

#include <Ntifs.h>
#include <ntddk.h>
#include <Ntstrsafe.h>

#define STR_LENGTH 0x1000

typedef struct _DATA
{
	LARGE_INTEGER time;
	wchar_t str[STR_LENGTH];
} DATA, *PDATA;

void _PassiveWrite(PDATA);

/* 外部から呼び出される関数。ここは IRQL <= DISPATCH_LEVEL であれば実行可能 */
void Write(PDATA d)
{
	WORK_QUEUE_ITEM workitem;
	PDATA data;

	/* 非ページプール上に領域を確保する */
	data = (PDATA)ExAllocatePoolWithTag(NonPagedPool, sizeof(DATA), 0x0616);
	if(data == NULL)
		return;

	/* データを準備する。 */
	ExInitalizeWorkItem(&workitem,_PassiveWrite,data);
	memcpy_s(data,sizeof(DATA),d,sizeof(DATA));

	/* Queue に登録する */
	ExQueueWorkItem(&workitem, DelayedWorkQueue);
}

/*
ExQueueWorkItem によって登録され、実行可能になった時に呼び出される。 
ここの IRQL は PASSIVE_LEVEL になる
*/
void _PassiveWrite(PDATA data)
{
	HANDLE handle;
	NTSTATUS status;
	UNICODE_STRING str;
	IO_STATUS_BLOCK statusblock = {0};

	/* ファイルオープン */
	{
		UNICODE_STRING path;
		OBJECT_ATTRIBUTES attr = {0};

		RtlInitUnicodeString(&path, L"\\??\\C:\\Logs\\nonohara.log");
		InitializeObjectAttributes(&attr,&path,
			OBJ_CASE_INSENSITIVE | 
			OBJ_KERNEL_HANDLE |
			OBJ_FORCE_ACCESS_CHECK,
			NULL, NULL);

		status = ZwCreateFile(
			&handle,FILE_APPEND_DATA,
			&attr,&statusblock,NULL,
			FILE_ATTRIBUTE_NORMAL,
			0,FILE_OPEN_IF,
			FILE_SYNCHRONOUS_IO_NONALERT,
			NULL,0
		);
		
	}

	if(!NT_SUCCESS(status))
	{
		ExFreePoolWithTag(data,0x0616);
		return;
	}

	/* 書き込む文字列を作る */
	str.Buffer = ExAllocatePoolWithTag(NonPagedPool,STR_LENGTH*2,0x1133);
	str.MaximumLength = STR_LENGTH*2;
	str.Length = 0;

	RtlUnicodeStringPrintf(&str,L"%I64u,%s\r\n",
		data->time,
		data->str
	);

	/* 書き込む */
	ZwWriteFile(handle, NULL, NULL, NULL, 
		&statusblock, str.Buffer, str.Length, NULL, NULL);

	/* バッファーをクリアする */
	ZwFlushBuffersFile(handle,&statusblock);
	ZwClose(handle);

	/* メモリを解放する */
	ExFreePoolWithTag(str.Buffer, 0x1133);
	ExFreePoolWithTag(data, 0x0616);
}

 

たとえばこれを応用して、

ドライバを開始したタイミングでファイルを開いてハンドルを握っておき

(その間、共有状態で CreateFile をしていないので他からのアクセスがロックされる。)

ドライバが停止するタイミングでバッファをフラッシュしてクローズする・・とか。

(その間バッファリングが効くので、たぶん良い感じになる・・はず)

 

後は何か、特定の動作があった場合に何かを呼び出すとかそういうのが書けます。

 

・・・とりあえず、個人的なメモ。

誰かドライバを書いていてこの記事で解決することがあれば・・。

Github 製エディタ ATOM for Windows.

3af1db48-d3dd-11e3-98dc-6066f8bc766f

 

7/4 追記:

http://atom.someguy123.com/#sthash.qFfvJCrT.21QtBbhw.dpbs

ここで自動的にビルドしたやつを公開しているっぽいです。(やっぱし。。)

でも安定版を追っかけてるっぽいので、
git から clone してきて・・とする場合は、やっぱり自分でビルドする必要がありそうです。


GitHub さんが出しているコードエディタ。

何となく話題になっていましたので使おうと思ったのですが、
Mac 版しかバイナリが提供されておらず、
しょうが無いのでソースを取ってきて Windows 向けにビルドしました。

 

やったことはこのページに従っただけ。
https://github.com/atom/atom/blob/master/docs/build-instructions/windows.md

ただ、手持ちの常用環境ではうまくビルド出来ず、
仮想環境を用意してやったところ成功したので、ビルド出来ていない人は多いのかも。

 

リファレンスについてはこのあたりを見ておくととても良い気がします。
http://qiita.com/spesnova/items/d3096d062d70e7385e9d

Mac 用ですが、ここの “cmd” をすべて “CTRL” に読み替えれば大丈夫です。

 

というわけで、以下のリンクよりダウンロード可能です。
(容量が大きいので Google Drive にリダイレクトされます)

( Atom-0.109.0-68f2bd5.zip – Google Drive )
http://go.mimumimu.net/1xqfwoz

ビルドしたバージョンは 0.109.0-68f2bd5 になります。

 

またこのプログラムは MIT ライセンスになります。
http://opensource.org/licenses/mit-license.php
http://sourceforge.jp/projects/opensource/wiki/licenses%2FMIT_license

 


Download Link:

http://go.mimumimu.net/1xqfwoz

Atom-0.109.0-68f2bd5.zip (Google Drive.)

セキュリティキャンプ 2014, Network クラスの Write-up

どうもみむらです。

先日、IPA が主催するセキュリティキャンプ 2014 の応募が締め切られまして、
噂では結構な倍率だったそうです。

http://www.ipa.go.jp/jinzai/camp/2014/zenkoku2014_sheet2.html

応募用紙は各クラス毎に大変難しい問題が用意されていて、
その中でも、特に今回は「ネットワーク・セキュリティ・クラス」の問題が難しいということで
少しやってみることにしました。


まずはマシンを用意。

Windows 7 Service Pack1 の環境を用意します。

01

02

 

 


問題ファイルを見てみる

ファイルをダウンロードして・・

03

テキストファイル!
ということで開いてみますと・・

04

・・・全然テキストファイルじゃないですね。
これが闇か。

 

くよくよしていられませんので早速がんばってみます。
読めそうな部分に、 “Server : Apache/2.2.16(debian)” というようなものがあります。

“Apache” という名前で調べてみると、どうやら Web サーバのようで、
もしかするとこれは HTTP の通信では無いかというところが分かります。

http://httpd.apache.org/

また、先頭が “ヤテイ” になっているところから、pcap ファイルということで間違いなさそうです。


切り出してみる

切り出してみれば何か分かるかもしれない!

05

HTTP なら “GET” があるはずということでそこ以降を切り出してみます。
・・・何か読めそうな文字が続きますが、それ以降が読めません。

帰ってくる内容だけ取り出してみることにします。
返答部分の最後が “Content-Type: text/plain” なのでそれを使います。

06

メモ帳で文字数をカウント。どうやら24文字のようです。

07

早速切り出してみます。でもやっぱりよく分からないデータはよく分からないデータ。

08

なにか余計なデータがあって、それによって化けてしまっているのかも!
・・てことで数字でデータを眺めてみます。

09

13,10,13,10..  改行ですね。必要ないので削りましょう。

10

うん。良い感じ。これなら読める・・?

11

やっぱりダメですか・・?

エンコーディングが間違っているのかもしれません。
変換する処理を書くのは面倒なので、ファイルに書き出してメモ帳に与えてみます。

12

・・・やっぱり出ません。ちょっと何か見落としているんでしょうか。

13

HTTP ヘッダのところに “Content-Encoding: gzip” とあります。

でもメモ帳ではそういうエンコーディングは対応していないようです。

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
仕様を見てみると、どうやらこれは gzip 圧縮を意味しているようです。

では残りの部分を gzip で展開させればデータが出ますね。

14

・・・いや、出ません。。

どうも調べてみると、通信時にパケットで分割されているらしく
そのまま後半の部分を与えても読めないようです。

確かに Content-Length をみるとなかなかに大きい値になっています。

TCP パケットの情報が分かれば・・というところですが、
「オプションフィールド」ということで可変長のデータが最後にくっついているため
後ろから見つけ出すのは問題がありそうです。

では pcap を頭から読むかとしても、
さすがに pcap を読むライブラリは入っていません。どうしたら良いでしょうか。


pcap を読む

困ったときは仕様を見ます。

まず pcap から
http://wiki.wireshark.org/Development/LibpcapFileFormat

最初に pcap ファイルに関する情報が入っているようです。 (pcap_hdr_s)
ですが余りここには必要な情報はなさそうですので
4+2+2+4+4+4+4 = 0x18 分読み飛ばします。

typedef struct pcap_hdr_s {
        guint32 magic_number;   /* magic number */
        guint16 version_major;  /* major version number */
        guint16 version_minor;  /* minor version number */
        gint32  thiszone;       /* GMT to local correction */
        guint32 sigfigs;        /* accuracy of timestamps */
        guint32 snaplen;        /* max length of captured packets, in octets */
        guint32 network;        /* data link type */
} pcap_hdr_t;

 

次に各パケット毎の情報が入っているようです。 (pcaprec_hdr_s)

取得時間とパケットのファイル上のサイズと実サイズが記述されています。

読み飛ばす際やパケットを処理する際に必要になるので、

8 – 12 にある incl_len を切り出しておきます。

typedef struct pcaprec_hdr_s {
        guint32 ts_sec;         /* timestamp seconds */
        guint32 ts_usec;        /* timestamp microseconds */
        guint32 incl_len;       /* number of octets of packet saved in file */
        guint32 orig_len;       /* actual length of packet */
} pcaprec_hdr_t;

Little Endian なので

[System.Bitconverter]::ToInt32($data | Select-Object -Skip (0x18 + 8) -First 4),0)

とかすると数字に変換できます。

incl_len で指定された範囲にあるデータが1つあたりのパケットになり、

incl_len 分読み飛ばした場所にはまた パケット毎のヘッダ情報とデータがあります。


パケットを読む

あとは仕様書に従って読んでいきます。

まずは イーサネットヘッダから

http://www.atmarkit.co.jp/ait/articles/0107/05/news001_3.html

最初のプリアンブル部分はどうも取得されていないようなので、宛先アドレス, 送信先アドレス, タイプ ってことで 6+6+2 = 14 バイト分読み飛ばします。

次に IP ヘッダ

http://www.atmarkit.co.jp/ait/articles/0304/04/news001_2.html

まずここの部分のサイズを取得する為にヘッダ長を取得します。

4bit ということで、8bit 単位でデータを取得した後で 0x0F を AND して取り出します。

出てきた数値は 32bit 単位ということですのでその値に ×4

コードとしては、

($ipheader[0] -band 0xF) * 4

 

とかすると、実際のサイズが出てきます。

サイズを求めたら、そのサイズ分読み飛ばします。

次に TCP ヘッダ

http://www.atmarkit.co.jp/ait/articles/0401/29/news080_2.html

まず、データ・オフセットを読みます。

これはアプリケーションが決めた送受信データ・・がどこから始まるかというのが書いてあり、

今回欲しいデータの始まる位置が分かります。

今回の場合は 上位 4bit ということで、 0xF で AND したあとで ÷16 を行い、

IP ヘッダと同じく 32bit 単位なので ×4 します。

コードとしては、

(($tcpheader[12] -band 0xf0) / 16) *4

こんな感じ。

そして、フラグも同様に切り出します。

[PSH], [ACK] を監視して PSH – ACK or [PSH,ACK] の区間を切り出してデータとして眺めるようにしてみます。

データとしては 先ほど取り出したデータオフセット(4bit)の直後から 12bit となります。

(要は 4bit + 8it)

コードとしてはこんな感じ:

($tcpheader[12] -band 0xF) * 256 + $tcpheader[13]

 

後は 先ほど取得したデータ・オフセット分だけデータを読み飛ばします。

ここまで来ればここから先は HTTP の通信データになります。(やったね!


HTTP を読む

何でしょうかこの、戻ってきた感じと達成感。

マリオRPG でもありましたよね。

クッパ城に最初に行って、なんか剣が突き刺さって・・

マップをぐるーっと回ってクッパ城に戻ってくる感じの。アレに似てますよね。

・・・・ちょっと時代が古いですかね。ごめんなさい。

 

さて。

まず HTTP 通信が行われているかどうかをさくっと判定します。

flag を見ても良いんですが 現在の位置が最初に取得したパケットのサイズを

パケットの頭の位置に足し合わせた値が一緒ならデータが無いので次のパケットを読み込みます。

ある場合にはデータを見ていきます。

・・とはいってもデータが取れているか気になるモンです。

なにか通信しているデータがある時にその中身を適当に表示させてみると。

15

・・・良い感じですね。良い感じでデータが出てきました。

あとは、Flag が PSH だけの間はそのデータを結合していき、

ACK or PSH,ACK が来たときにデータが完全に送られたと判断して、処理を行います。

データが取れたら、後は一番最初でやったように HTTP ヘッダを読み飛ばして下の部分だけ切り出し、

Gzip を展開します。

16

C# の GzipStream が使えますので、それを呼び出します。

http://msdn.microsoft.com/ja-jp/library/system.io.compression.gzipstream(v=vs.110).aspx

 

あとはそれをファイルに書き出すと・・。

17

テキストファイルが出てきました! これなら読めそうです。

・・・でも、ここからまた問題を解く必要がありそうです。

 

最後に今回作ったコードを貼り付けておきます:

$file = Get-Content "C:\Users\yahihashoo\Desktop\000038855.txt" -Encoding Byte
$text = New-Object System.String($file,0,$file.Length)
$p = 0x18 # PCAP の Header 分
$fin = 0
$stream = New-Object System.IO.MemoryStream
$output = New-Object System.IO.FileStream "C:\Users\yahihashoo\Desktop\question.txt",([IO.FileMode]::Create),([IO.FileAccess]::Write),([IO.FileShare]::ReadWrite)

do{
    $size = [System.BitConverter]::ToInt32(($file | Select-Object -Skip ($p+4+4) -First 4),0) #packet size.
    $p += 4 * 4 # PCAP の各パケット分
    
    $packet = ($file | Select-Object -Skip $p -First $size)
    $packetpos = 6 + 6 + 2 # Ethernet Frame.
    $srcip = $packet[$packetpos + 12] * 0x1000000 + $packet[$packetpos + 13] * 0x10000 + $packet[$packetpos + 14] * 0x100 + $packet[$packetpos + 15]
    $dstip = $packet[$packetpos + 16] * 0x1000000 + $packet[$packetpos + 17] * 0x10000 + $packet[$packetpos + 18] * 0x100 + $packet[$packetpos + 19]
    $packetpos += ($packet[$packetpos] -band 0xF) * 4 # IP Frame.
    $srcport = $packet[$packetpos] * 256 + $packet[$packetpos+1]
    $dstport = $packet[$packetpos+2] * 256 + $packet[$packetpos+3]
    $seq = $packet[$packetpos + 4] * 0x1000000 + $packet[$packetpos + 5] * 0x10000 + $packet[$packetpos + 6] * 0x100 + $packet[$packetpos + 7]
    $flag = (($packet[$packetpos + 12] -band 0x0f) * 256) + $packet[$packetpos + 13]
    $packetpos += (($packet[$packetpos + 12] -band 0xf0) / 16) * 4
    if ($packetpos -ne $packet.Length)
    {
        $data = $packet | Select-Object -Skip $packetpos
        $html = [System.Text.Encoding]::ASCII.GetString($data)
        $htmlpos = $html.IndexOf("`r`n`r`n")
        if($htmlpos -lt 0)
        {
            $htmlpos = 0
            $htmlbody = $data
        } else {
            Write-Host ([System.Text.Encoding]::ASCII.GetString(($data | Select-Object -First $htmlpos)))
            Write-Host "---------------------"
            $htmlpos += 4
            $htmlbody = $data | Select-Object -Skip $htmlpos
        }
        if($null -ne $htmlbody -and $htmlbody.Length -ne -1)
        {
            $stream.Write($htmlbody,0,$htmlbody.Length)
        }
        if(($flag -band 0x8) -eq 0x8) #ACK
        {
            if($stream.Length -gt 0)
            {
                $stream.Position = 0
                $gzipstream = New-Object System.IO.Compression.GzipStream $stream,([IO.Compression.CompressionMode]::Decompress)
                $buf = New-Object byte[](1024)
                do
                {
                    $read = $gzipstream.Read($buf,0,1024)
                    $output.Write($buf,0,$read)
                }while($read -gt 0)
                $output.Flush()
                $gzipstream.Close()
            }
            $stream.Close()
            $stream = New-Object System.IO.MemoryStream
        }
    }
    
    $p += $size
}while($p -lt $file.Length)
$output.Flush()
$output.Close()

説明文中で書いた読み飛ばしよりも、若干丁寧に読んでます。

実際は シーケンス番号がちゃんと続いてるかとか、IP, PORT あたりの情報も使って

切り出そうと思ったのですが今回はご愛敬ということで。

(ただし、出せるのでやろうと思えば。。でもパケットの順番を見ていないので逆になったりとかするとダメですし、再送とかが発生しているやつを食べさせても処理できません。)

 

それと注意点として。

ネットワーク上の IP アドレスの値やポート番号、シーケンス番号などは

ビッグエンディアンで取り扱われています。

そのため、一つずつ取り出して掛け合わせるというような演算で求める必要があります。

( Linq とか使って4バイト出して反転してリトルエンディアンとして処理しても求まります。)

この辺を最初忘れていて、値を取り出すのに時間がかかったのは秘密。

 


もっとずっと簡単に:

解けるらしいですね。

http://yagihashoo.com/archives/683

なんでも、wireshark というソフトウェアを使えば

すぐに内容が抽出できるということで。

いやー・・。なんというかなんというか。。

ツールを知っていることは重要ですね。

 


最後に:

もちろん、今回の記事はジョークです。

こういうことになってしまった手前、やらねば、と。

もちろん Wireshark は普段使いますし、普段使うマシンにはすべて入れてあります。

こういう縛りなので、Windows 7 のマシンを一つ新規で作成して、

Windows Update を OFF にしてやってました。

18

PowerShell は最初から入っていることを知っていましたし、

C:\Windows\Microsoft.NET 以下に csc.exe という形で C# のコンパイラが最初から

入っていることも知ってました。

・・ま、PowerShell の方が、より面白そうということで。

 

・・・なんでこんな無駄な事をやったかってのがあるかと思いますが、

「こんなことも出来るんだよ!」と、

MSP (Microsoft Student Partners ) の端くれとしての意地、とでも言いましょうか。

大道芸ですごい無駄な事を真面目にやるような、

そんな、なんか気持ちを味わってもらえたらなと思ってやりました。

・・・もし感じて頂けたら本望です。

 


そして・・。

もしこの記事を今年のセキュリティ・キャンプの応募者の方が見ていて、

全力で端折った部分の内容がきちんと分かるということであれば、

応募用紙はさくさくっと書けたのでは無いかなと思います。

是非とも、セキュリティキャンプを通して今後の人生が変わるような、

そんなステキな体験が出来るように祈っています。

Python で “error: Unable to find vcvarsall.bat” が出た場合。

どうもみむらです。

今回は Windows で Python 使ってて、
setup.py install とかしたときに、 “error: Unable to find vcvarsall.bat” とか出た場合について。

下記のウェブサイトにて記述を見つけたのですが、一応自分向けのメモとして書き直し。
街角のリブロガー pythonでvcvarsall.batエラーが出る。



1.使っている Python のビルドされた環境を確かめる。

起動直後に現れる “MSC v.1500” というような表記からバージョンを判定しても良いのですが、
( Visual Studio を使っている人なら分かるかも・・? )

次のコードを実行して判定します。

from distutils.msvccompiler import *
get_build_version()

出力結果に “9.0” や “10.0” と出るかと思います。

 

2.レジストリに値を書き込む

値を次の場所に書き込みます:

キー名: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\(Python の MSVC のバージョン)\Setup\VC

値名:ProductDir

値:(現在使っている Visual Studio の VC フォルダへのパス)

 

例として、私の環境で使っている Python 2.7 が MSVC 9.0 環境でビルドされており、

一方で私自身は Visual Studio 2013 を使っています。

この場合値は次のようになります:

キー名: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\9.0\Setup\VC

値名:ProductDir

値:C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC

image

 

また、1で 10.0 と出た場合は、

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0\Setup\VC に。

その他 x86 (32bit) 環境の場合は、

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Setup\VC になって、

パスも C:\Program Files\Microsoft Visual Studio 12.0\VC

等になるかと思います。

・・取り急ぎ、メモ。

Windows で easy_install と pip を使えるようにする

みむらです。ちょっとメモ。

 


1.事前に PATH に C:\Python27 と C:\Python27\Scripts を通しておく

私の環境だと、Python 2 系と Python 3系が入っていますが、
どっちかに通しておけば、 “py” で 2系、 “py –3” で 3系 を起動できるので良い感じ。

“python” を実行したときに 2 を使う人か 3 を使う人かでどっちかに通しておけば良いかなと。

・・私は 2 系の方をまだ使うので 2.7 に通しました。 

 

ちなみに PATH を通すには:

1.「システムとセキュリティ」を選択
image

 

2.「システム」を選択

image

 

3.「システムの詳細設定」を選択

image

 

4.「詳細設定」タブ内の「環境変数」を選択

image

 

5.Path を追記する

“PATH” という項目が無い場合は、「新規」をクリック

image

 

”PATH” という項目がある場合は、その項目をダブルクリック

image

 

でもって、こんな感じで設定する。

image

すでに項目がある場合は、そのパスの後ろに “;” 書いてパスを追記する。

複数の場所に PATH を設定したい場合は、”;” で区切って書いていく。

 


2.easy_install をインストールする。

https://bootstrap.pypa.io/ez_setup.py

ここから ez_setup.py をダウンロードしてきて、ダブルクリックで実行
勝手にファイルをダウンロードしてインストールしてくれる。

・・もし、ダブルクリックしても動かない場合は
コマンドプロンプトを開いて、

python ez_setup.py

と打ち込むと動きます。

 

また、コマンドプロンプトを開いてそこまで移動するのが面倒・・という場合は、

Shift キーを押しながら右クリックをしますと、下図のような項目が出ますので、

わざわざ移動せずともコマンドプロンプトを指定したフォルダをカレントディレクトリとして起動することが出来ます。

image

image

 


3.pip をインストール

コマンドプロンプトを開いて、

easy_install pip

を実行すると導入されます。


後は PATH を通してありますので、

image

こんな感じで、いきなり “pip” と入力しても動くようになります。

 

・・ひとまず、参考まで。