N=1

主にコンピュータ技術関連のことを投稿。 / 投稿は個人の意見であり所属団体の立場を代表するものではありません。

DarkCTF Write up

あまり勉強の蓄積は効かず、手癖で解けたものだけでした。

Linux

Find-Me

Mr.Wolf was doing some work and he accidentally deleted the important file can you help him and read the file? Note: All players will get individual container. ssh ctf@findme.darkarmy.xyz -p 10000 password: wolfie

wolf1 というユーザでログインとなる。/home/wolf1 がホームで、ls しても空っぽ。 ls /home すると、ほかに wolf2 が居るのが分かる。sudo は無いが、su はできる。

雑に deleted file recovery linuxぐぐる。対象サーバは docker 環境なので /dev/sd* などのブロックデバイスは存在しない。よって raw なバイナリを探ったりはできない。そういう情報は除外して調べた。

https://unix.stackexchange.com/questions/80270/unix-linux-undelete-recover-deleted-files

f=$(ls 2>/dev/null -l /proc/*/fd/* | fgrep "$1 (deleted" | awk '{print $9}')

/proc/*/fd/* になにか出ているらしい。

$ ls /proc/*/*fd*
...

/proc/10/fd:
total 0
lr-x------ 1 wolf1 wolf1 64 Sep 25 23:32 0 -> /dev/null
l-wx------ 1 wolf1 wolf1 64 Sep 25 23:32 1 -> /dev/null
l-wx------ 1 wolf1 wolf1 64 Sep 25 23:32 2 -> /dev/null
lr-x------ 1 wolf1 wolf1 64 Sep 25 23:32 3 -> '/home/wolf1/pass (deleted)'

...

怪しいファイルがあった。

$ cp /proc/10/fd/3 pass
$ cat pass
mysecondpassword123

mysecondpassword なので、wolf2 のパスワードと推察。

$ su wolf2
Password: mysecondpassword123
(wolf2)$ cd /home/wolf2
$ ls -R
.:
bin  etc  flag_might_be_here  not_so_important_files  not_sure  proc  somefiles  tmp

./bin:
wolf2

./bin/wolf2:
bin  tmp

./bin/wolf2/bin:

./bin/wolf2/tmp:

./etc:

./flag_might_be_here:

./not_so_important_files:

./not_sure:

./proc:
a  f  g  l

./proc/a:

./proc/f:

./proc/g:
nice_work

./proc/l:

./somefiles:

./tmp:
$ cat ./proc/g/nice_work
darkCTF{you are standing on the flag}

}!!!kr0w_3c1n_hha0w{FTCkrad

}!!!kr0w_3c1n_hha0w{FTCkrad をリバースするとフラグになっている

Web

Source

Don't know source is helpful or not !!

http://source.darkarmy.xyz/

ソースコード index.php の配布もある。

$web = $_SERVER['HTTP_USER_AGENT'];
if (is_numeric($web)){
      if (strlen($web) < 4){
          if ($web > 10000){
                 echo ('<div class="w3-panel w3-green"><h3>Correct</h3>
  <p>darkCTF{}</p></div>');

UA 偽装すればいいが、問題は値だ。

PHP において

  1. is_numeric が true
  2. 文字列長が1から3
  3. 10000 との比較で大きいと判定される

という文字列にしなければならない。単に整数 999 では、条件1,2 は通るが 3 は FALSE になる。

https://www.php.net/manual/en/function.is-numeric.php

Finds whether the given variable is numeric. Numeric strings consist of optional whitespace, optional sign, any number of digits, optional decimal part and optional exponential part. Thus +0123.45e6 is a valid numeric value. Hexadecimal (e.g. 0xf4c3b00c) and binary (e.g. 0b10100111001) notation is not allowed.

is_numeric()の判定では整数だけではなく、数値リテラルならなんでも通すらしい。浮動小数リテラルを試してみよう。1e9で、1x109=1000000000になる。

$ curl -A "1e9" http://source.darkarmy.xyz/
<html>
    <head>
        <title>SOURCE</title>
        <style>
            #main {
    height: 100vh;
}
        </style>
    </head>
    <body><center>
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<div class="w3-panel w3-green"><h3>Correct</h3>
  <p>darkCTF{changeing_http_user_agent_is_easy}</p></div></center>
<!-- Source is helpful -->
    </body>
</html>

done.

Apache Logs

Our servers were compromised!! Can you figure out which technique they used by looking at Apache access logs. flag format: DarkCTF{}

Apache のログが添付されている。

logs.ctf

大半はヒントになっていないアクセスだ。ログにある対象サーバはローカルネットワークらしく、実際にはアクセスできない。だからログの内容から読み取る必要がある。

怪しいところをピックアップ

192.168.32.1 - - [29/Sep/2015:03:37:34 -0400] "GET /mutillidae/index.php?page=user-info.php&username=%27+union+all+select+1%2CString.fromCharCode%28102%2C+108%2C+97%2C+103%2C+32%2C+105%2C+115%2C+32%2C+83%2C+81%2C+76%2C+95%2C+73%2C+110%2C+106%2C+101%2C+99%2C+116%2C+105%2C+111%2C+110%29%2C3+--%2B&password=&user-info-php-submit-button=View+Account+Details HTTP/1.1" 200 9582 "http://192.168.32.134/mutillidae/index.php?page=user-info.php&username=something&password=&user-info-php-submit-button=View+Account+Details" "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"

URL Decode "GET /mutillidae/index.php?page=user-info.php&username=' union all select 1,String.fromCharCode(102, 108, 97, 103, 32, 105, 115, 32, 83, 81, 76, 95, 73, 110, 106, 101, 99, 116, 105, 111, 110),3 --+&password=&user-info-php-submit-button=View Account Details HTTP/1.1"

どうやら SQL インジェクションを試みている。

102 108 97 103 32 105 115 32 83 81 76 95 73 110 106 101 99 116 105 111 110 を ASCII にすると、flag is SQL_Injection だがこれ自体はフラグではなかった。

次はこれ。

192.168.32.1 - - [29/Sep/2015:03:38:46 -0400] "GET /mutillidae/index.php?csrf-token=&username=CHAR%28121%2C+111%2C+117%2C+32%2C+97%2C+114%2C+101%2C+32%2C+111%2C+110%2C+32%2C+116%2C+104%2C+101%2C+32%2C+114%2C+105%2C+103%2C+104%2C+116%2C+32%2C+116%2C+114%2C+97%2C+99%2C+107%29&password=&confirm_password=&my_signature=&register-php-submit-button=Create+Account HTTP/1.1" 200 8015 "http://192.168.32.134/mutillidae/index.php?page=register.php" "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"

URL Decode

GET /mutillidae/index.php?csrf-token=&username=CHAR(121, 111, 117, 32, 97, 114, 101, 32, 111, 110, 32, 116, 104, 101, 32, 114, 105, 103, 104, 116, 32, 116, 114, 97, 99, 107)&password=&confirm_password=&my_signature=&register-php-submit-button=Create Account HTTP/1.1

121, 111, 117, 32, 97, 114, 101, 32, 111, 110, 32, 116, 104, 101, 32, 114, 105, 103, 104, 116, 32, 116, 114, 97, 99 は、you are on the right track

同様に次の部分。

192.168.32.1 - - [29/Sep/2015:03:39:46 -0400] "GET /mutillidae/index.php?page=client-side-control-challenge.php HTTP/1.1" 200 9197 "http://192.168.32.134/mutillidae/index.php?page=user-info.php&username=%27+union+all+select+1%2CString.fromCharCode%28102%2C%2B108%2C%2B97%2C%2B103%2C%2B32%2C%2B105%2C%2B115%2C%2B32%2C%2B68%2C%2B97%2C%2B114%2C%2B107%2C%2B67%2C%2B84%2C%2B70%2C%2B123%2C%2B53%2C%2B113%2C%2B108%2C%2B95%2C%2B49%2C%2B110%2C%2B106%2C%2B51%2C%2B99%2C%2B116%2C%2B49%2C%2B48%2C%2B110%2C%2B125%29%2C3+--%2B&password=&user-info-php-submit-button=View+Account+Details" "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"

http://192.168.32.134/mutillidae/index.php?page=user-info.php&username=' union all select 1,String.fromCharCode(102,+108,+97,+103,+32,+105,+115,+32,+68,+97,+114,+107,+67,+84,+70,+123,+53,+113,+108,+95,+49,+110,+106,+51,+99,+116,+49,+48,+110,+125),3 --+&password=&user-info-php-submit-button=View Account Details

flag is DarkCTF{5ql_1nj3ct10n}

PHP information

Let's test your php knowledge. Flag Format: DarkCTF{} http://php.darkarmy.xyz:7001

開くと、HTML と PHPソースコードが表示される。どうやらこのサイトのコードのようだ。

クエリパラメータおよび User Agent を操作することで条件分岐を通し、FLAG を出すということのようだ。

ソースコードの if を通すように curl を組んでいく。

    if (isset($_GET['karma']) and isset($_GET['2020'])) {
        if ($_GET['karma'] != $_GET['2020'])
        if (md5($_GET['karma']) == md5($_GET['2020']))
            echo "<h1 style='color: chartreuse;'>Flag : $flag_3</h1></br>";
        else
            echo "<h1 style='color: chartreuse;'>Wrong</h1></br>";
    }

ここだけ難しい。文字列としては違うけど md5() で比較すると同じになる? MD5 で重複する文字列も探したが、PHP 的には MD5 を比較する際のコーディングの間違いをついたほうがスマートのようだ。

http://peccu.hatenablog.com/entry/2015/05/23/000000

比較が===ではなく==であるから、ということがわかったとして、なぜ同じになるのか。 上記2つの文字列のmd5ハッシュ値が0eで始まり、以降数字が並ぶからです。

0e で始まる md5 の文字列を用意すればいいようだ。このサイトの例をそのまま使わせてもらう。

コンソールで見ると読みづらいので一旦 index.html に保存してからブラウザで開いた。

curl -A 2020_the_best_year_corona 'http://php.darkarmy.xyz:7001/?darkctf=2020&ctf2020=WkdGeWEyTjBaaTB5TURJd0xYZGxZZz09&karma=Password147186970!&2020=240610708' -o index.html

Flag : DarkCTF{

Flag : very_

Flag : nice

Flag : _web_challenge_dark_ctf}

Forensics

AW

動画にノイズが入っているように聞こえる。Spectre.mp4 というファイル名から Spectrogram のフォレンジックを疑う。

ffmpeg -i Spectre.mp4 -vn -acodec libmp3lame -f mp3 Spectre.mp3

Audacity で Spectre.mp3 を開いて Spectrogram で見ると、フラグが浮き上がる。

darkCTF{1_l0v3_5p3ctr3_fr0m_4l4n}

CSAW CTF Quals 2020 Write-up

https://ctf.csaw.io

CTFはバイナリやセキュリティの知識を持たずに遊びで参加しているので、正攻法では無さそうな解法をしてしまっている。しかも最低点の問題を解くのがやっと。問題を始めてから勉強しているので注意。

rev, baby_mult

progam.txt として以下のテキストが与えられる

85, 72, 137, 229, 72, 131, 236, 24, 72, 199, 69, 248, 79, 0, 0, 0, 72, 184, 21, 79, 231, 75, 1, 0, 0, 0, 72, 137, 69, 240, 72, 199, 69, 232, 4, 0, 0, 0, 72, 199, 69, 224, 3, 0, 0, 0, 72, 199, 69, 216, 19, 0, 0, 0, 72, 199, 69, 208, 21, 1, 0, 0, 72, 184, 97, 91, 100, 75, 207, 119, 0, 0, 72, 137, 69, 200, 72, 199, 69, 192, 2, 0, 0, 0, 72, 199, 69, 184, 17, 0, 0, 0, 72, 199, 69, 176, 193, 33, 0, 0, 72, 199, 69, 168, 233, 101, 34, 24, 72, 199, 69, 160, 51, 8, 0, 0, 72, 199, 69, 152, 171, 10, 0, 0, 72, 199, 69, 144, 173, 170, 141, 0, 72, 139, 69, 248, 72, 15, 175, 69, 240, 72, 137, 69, 136, 72, 139, 69, 232, 72, 15, 175, 69, 224, 72, 15, 175, 69, 216, 72, 15, 175, 69, 208, 72, 15, 175, 69, 200, 72, 137, 69, 128, 72, 139, 69, 192, 72, 15, 175, 69, 184, 72, 15, 175, 69, 176, 72, 15, 175, 69, 168, 72, 137, 133, 120, 255, 255, 255, 72, 139, 69, 160, 72, 15, 175, 69, 152, 72, 15, 175, 69, 144, 72, 137, 133, 112, 255, 255, 255, 184, 0, 0, 0, 0, 201

127 以上の値があるから、単純な ASCII として読めるものではないのは確かだ。

const src = [85, 72, 137, 229, 72, 131, 236, 24, 72, 199, 69, 248, 79, 0, 0, 0, 72, 184, 21, 79, 231, 75, 1, 0, 0, 0, 72, 137, 69, 240, 72, 199, 69, 232, 4, 0, 0, 0, 72, 199, 69, 224, 3, 0, 0, 0, 72, 199, 69, 216, 19, 0, 0, 0, 72, 199, 69, 208, 21, 1, 0, 0, 72, 184, 97, 91, 100, 75, 207, 119, 0, 0, 72, 137, 69, 200, 72, 199, 69, 192, 2, 0, 0, 0, 72, 199, 69, 184, 17, 0, 0, 0, 72, 199, 69, 176, 193, 33, 0, 0, 72, 199, 69, 168, 233, 101, 34, 24, 72, 199, 69, 160, 51, 8, 0, 0, 72, 199, 69, 152, 171, 10, 0, 0, 72, 199, 69, 144, 173, 170, 141, 0, 72, 139, 69, 248, 72, 15, 175, 69, 240, 72, 137, 69, 136, 72, 139, 69, 232, 72, 15, 175, 69, 224, 72, 15, 175, 69, 216, 72, 15, 175, 69, 208, 72, 15, 175, 69, 200, 72, 137, 69, 128, 72, 139, 69, 192, 72, 15, 175, 69, 184, 72, 15, 175, 69, 176, 72, 15, 175, 69, 168, 72, 137, 133, 120, 255, 255, 255, 72, 139, 69, 160, 72, 15, 175, 69, 152, 72, 15, 175, 69, 144, 72, 137, 133, 112, 255, 255, 255, 184, 0, 0, 0, 0, 201];

const hex = src.map(e => '\\x' + e.toString(16).padStart(2, '0')).join('');

console.log(hex);

hex 表記にしてみる。

\x55\x48\x89\xe5\x48\x83\xec\x18\x48\xc7\x45\xf8\x4f\x00\x00\x00\x48\xb8\x15\x4f\xe7\x4b\x01\x00\x00\x00\x48\x89\x45\xf0\x48\xc7\x45\xe8\x04\x00\x00\x00\x48\xc7\x45\xe0\x03\x00\x00\x00\x48\xc7\x45\xd8\x13\x00\x00\x00\x48\xc7\x45\xd0\x15\x01\x00\x00\x48\xb8\x61\x5b\x64\x4b\xcf\x77\x00\x00\x48\x89\x45\xc8\x48\xc7\x45\xc0\x02\x00\x00\x00\x48\xc7\x45\xb8\x11\x00\x00\x00\x48\xc7\x45\xb0\xc1\x21\x00\x00\x48\xc7\x45\xa8\xe9\x65\x22\x18\x48\xc7\x45\xa0\x33\x08\x00\x00\x48\xc7\x45\x98\xab\x0a\x00\x00\x48\xc7\x45\x90\xad\xaa\x8d\x00\x48\x8b\x45\xf8\x48\x0f\xaf\x45\xf0\x48\x89\x45\x88\x48\x8b\x45\xe8\x48\x0f\xaf\x45\xe0\x48\x0f\xaf\x45\xd8\x48\x0f\xaf\x45\xd0\x48\x0f\xaf\x45\xc8\x48\x89\x45\x80\x48\x8b\x45\xc0\x48\x0f\xaf\x45\xb8\x48\x0f\xaf\x45\xb0\x48\x0f\xaf\x45\xa8\x48\x89\x85\x78\xff\xff\xff\x48\x8b\x45\xa0\x48\x0f\xaf\x45\x98\x48\x0f\xaf\x45\x90\x48\x89\x85\x70\xff\xff\xff\xb8\x00\x00\x00\x00\xc9

最初の 0x55 は関数の開始である push pbx として頻出なので、x86 系の命令と推察できる。さらに、0x48 がよく出てくる。これは x86_64 において 64bit 命令の場合のプレフィックスなので、x86_64 のバイナリの断片である可能性が高い。

さて、これをどうやって実行可能にしようか。

  • シェルコードとして exploit するテスターを作る?
  • このままリンカに通せないか?
  • ゴリ押しで読んで理解する?

残念ながら私は pwn の知識も、シェルコードの知識も無い。

(1) シェルコードとして exploit するテスターを作る?

ウェブから shell code tester みたいな検索をしてシェルコードとして実行してみたが、segmentation fault になってしまう。

(2) このままリンカに通せないか?

一旦 disassemble して、それっぽいラベルをつけておいた。実行が終わる前のところにブレークポイントをつけれるようにもした。

https://onlinedisassembler.com/odaweb/

program.s

.globl main
	.type main, @function
main:
    push   %rbp
    mov    %rsp,%rbp
    sub    $0x18,%rsp
    movq   $0x4f,-0x8(%rbp)
    movabs $0x14be74f15,%rax
    mov    %rax,-0x10(%rbp)
    movq   $0x4,-0x18(%rbp)
    movq   $0x3,-0x20(%rbp)
    movq   $0x13,-0x28(%rbp)
    movq   $0x115,-0x30(%rbp)
    movabs $0x77cf4b645b61,%rax
    mov    %rax,-0x38(%rbp)
    movq   $0x2,-0x40(%rbp)
    movq   $0x11,-0x48(%rbp)
    movq   $0x21c1,-0x50(%rbp)
    movq   $0x182265e9,-0x58(%rbp)
    movq   $0x833,-0x60(%rbp)
    movq   $0xaab,-0x68(%rbp)
    movq   $0x8daaad,-0x70(%rbp)
    mov    -0x8(%rbp),%rax
    imul   -0x10(%rbp),%rax
    mov    %rax,-0x78(%rbp)
    mov    -0x18(%rbp),%rax
    imul   -0x20(%rbp),%rax
    imul   -0x28(%rbp),%rax
    imul   -0x30(%rbp),%rax
    imul   -0x38(%rbp),%rax
    mov    %rax,-0x80(%rbp)
    mov    -0x40(%rbp),%rax
    imul   -0x48(%rbp),%rax
    imul   -0x50(%rbp),%rax
    imul   -0x58(%rbp),%rax
    mov    %rax,-0x88(%rbp)
    mov    -0x60(%rbp),%rax
    imul   -0x68(%rbp),%rax
    imul   -0x70(%rbp),%rax
    mov    %rax,-0x90(%rbp)
    mov    $0x0,%eax
break:
    leaveq 

Linux で $ gcc program.sとすれば a.out として一応実行可能にはなるが、出力等は特にない。gdbレジスタをみたけどよくわからなかった。

(3) ゴリ押しで読んで理解する?

普通の CTF 慣れした人とは全然違うアプローチになってしまうが、そのまま読んでみた。x86 のことも全然知らなかったので勉強した。

命令一覧

  • push op
    • rsp をスタックを積む方向に動かす(デクリメント)
    • op の値をそこに格納
  • mov op1, op2
    • op1 => op2
  • movq
    • mov と同じだが 64bit であることを明示
  • movabs
    • これも、オペランドが64bitのときにGASが付与した名前であり、mov と同じ
  • sub op1, op2
    • op2 - op1 => op1
  • imul op1, op2
    • op1 * op2 => op1
  • leaveq
    • rsp => rbp
    • rbp を pop

レジスタ一覧

  • rbp: 64bit Base Pointer
  • rsp: 64bit Stack Pointer
  • rax: 64bit Accumulator
  • eax: 32bit Accumulator

(レジスタ命名規則)

  • X: eXtend 16bit
    • アキュムレータは8bit CPU では AL だったが、16bit になった際に AX になった
  • E: Enhanced 32bit
    • x86 では AX から EAX になった
  • R: Register 64bit
    • x86_64 では EAX から RAX になった

記号

  • %: レジスタの前につける
  • $: 定数の前につける

BP, SP, push の動き

知識が揃ったところで読んでいった。手作業で計算した。結果はこうなった。

レジスタ
    rbp: rsp の値
    rax: 306772346d7d
スタック
    元の rbp の値 <-
    --底部--
メモリ
    rsp-0x90:    306772346d7d
    rsp-0x88:    6c31645f7072
    rsp-0x80:    73757033725f7634
    rsp-0x78:    666c61677b
    rsp-0x70:    8daaad  
    rsp-0x68:    aab
    rsp-0x60:    833
    rsp-0x58:    182265e9
    rsp-0x50:    21c1
    rsp-0x48:    11
    rsp-0x40:    2
    rsp-0x38:    77cf4b645b61
    rsp-0x30:    115
    rsp-0x28:    13
    rsp-0x20:    3
    rsp-0x18:    4    
    rsp-0x10:    14be74f15
    rsp-0x8:    4f
    rsp: ?

imulで計算した結果を保存しているらしき -0x78 ~ -0x90 を抽出するとこうなる
666c61677b
73757033725f7634
6c31645f7072
306772346d7d

ASCII として読むとフラグになっている。

正攻法は、シェルコードとして実行して segfault か何かで止まったところを gdb でメモリ確認するといいのかもしれないが、まだ私にはできなかった。

web, widthless

http://web.chal.csaw.io:5018/ が与えられている。

ML 購読フォームがある、作りかけのサイトという体裁。

  • Email の POST のフォームは HTTP としては動作するが特に進展しない。csrf_token が設定されている。
  • レスポンスヘッダにはクッキーがある程度で怪しいところは無い。

ソースを読むとヒントと思われるメッセージがあった。

    <!-- zwsp is fun! -->

また、developer tool で見ると、body 下部に #8203;​​ などの特殊文字がぎっしり入っていた。

zwsp で調べると、"ゼロ幅スペースステガノグラフィ"に関連するツールを見つけた。

github.com

HTML 全体をこのツールに通す

const fs = require('fs');
const zwspSteg = require('zwsp-steg');

const html = fs.readFileSync('index.html', 'utf-8');
const decoded = zwspSteg.decode(html);
console.log(decoded);

YWxtMHN0XzJfM3o= が得られた。base64 でデコードすると、alm0st_2_3zとなる。これをフォームに入力すると新たな本文が追加される。

/ahsdiufghawuflkaekdhjfaldshjfvbalerhjwfvblasdnjfbldf/<pwd>

pwd は alm0st_2_3z のことだと判断し、http://web.chal.csaw.io:5018/ahsdiufghawuflkaekdhjfaldshjfvbalerhjwfvblasdnjfbldf/alm0st_2_3z へアクセスしてみる。すると今度は HTML 全体にゼロ幅スペースが散りばめられていた。

また zwsp-steg で処理を試みるも、ブラウザでレンダーされたものをコピペしてもうまくデコードできなかった。きちんと curl で無加工のペイロードを取ってくる。

$ curl http://web.chal.csaw.io:5018//ahsdiufghawuflkaekdhjfaldshjfvbalerhjwfvblasdnjfbldf/alm0st_2_3z  -o index.html

zwsp-steg へまた通すと、755f756e6831645f6d33 が得られた。2桁ずつ ASCII 読みすると u_unh1d_m3 となる。

これをフォームで送信すると次のヒントが出る。

/19s2uirdjsxbh1iwudgxnjxcbwaiquew3gdi/<pwd1>/<pwd2>

pwd1, pwd2 はこれまで得た文字列だと判断し、
http://web.chal.csaw.io:5018//19s2uirdjsxbh1iwudgxnjxcbwaiquew3gdi/alm0st_2_3z/u_unh1d_m3 へ。

するとフラグを得られる。

Time Timer を買った

集中力向上のツールとして Time Timer というのを買ってみた。高くて気軽に手を出せないのにあまり日本語でのレポートが無いのでしたためておく。

私は時間の経過を感じるのが苦手だからいつもだらだらと作業してしまう。したがって25分のタイマーを区切りに作業するポモドーロテクニックを導入してみたりしたのだが、私の要求に沿ったツールがなかなか無かった。

要件

  • 視認性が良い
  • アラーム音を切れる
  • 面積などで視覚的に経過時間がわかる
  • 単機能

これまで検討したもの

  • スマホやパソコンでやるとウィンドウが背面に回ってしまったり起動をし忘れたりして存在をすぐに忘れてしまう。また、デジタル式だと時間の量が感覚的に掴めない。
  • アナログのキッチンタイマーはカチカチと音がなるものが多く、けたたましいアラームを無効にすることができない。オフィスでは絶対に使えない。

Time Timer

この要件だと Time Timer が最適だった。

www.timetimer.com

  • 面積で残り時間を視認できる
  • 単機能
  • アラームを切れる
  • 時計のチクタク音が無い

もともと子供やASDなどもスコープに入れたツールであり押さえるべきところが押さえられている。

欠点は価格だ。安いモデルでも5,000円以上する。Time Timer 自体は学生の頃から知っていたが高すぎて手が出なかった。自作も検討していたがずっと放置していた。

忘れていたころに、類似の IoT デバイスで Kotobo という、30分タイマーを通知するだけのボタンが登場した。とても欲しいと思ったがこれはなんと 12,980円もする。

koto-ha.co.jp


これを買う前に類似の機能を持ったより安価なもので効果を知っておきたいなと思い、ふと Time Timer を思い出して Kotobo の半値以下なら買ってしまうか、と決断したのである。

Unboxing

インテリアに馴染むような小型モデルで、Time Timer MOD というのがあり 5,000円ちょっとで買った。確か昔は買える場所が少なかったような気がするが、今では楽天の玩具屋などでも扱っている。

f:id:ishn:20191228094549j:plain

外箱はおしゃれ。

f:id:ishn:20191228094739j:plain

内容物はこれ。

f:id:ishn:20191228094828j:plain

本体は柔らかいシリコンカバーがついていて雑に扱ってもいいようになっている。このカバーにカラーバリエーションがあるらしく、それで MOD というらしい。
f:id:ishn:20191228095057j:plain

背面はアラーム音のスイッチと電池蓋がある。電池は同梱されておらず、単三電池1本と蓋を開けるためのメガネ用ドライバーが必要。蓋、ネジは固めなので、小さい子供に渡しても問題なさそうだ。

www.youtube.com

文字盤中央の取っ手を回すだけで自動でカウントダウンが始まる。アラーム音はこんな感じで4回鳴って終了。ずっと鳴り続けたりはしないのが素晴らしい。

使用感

良い

  • 赤い面積で残り時間が直感的にわかる。これを求めていた。
  • 中央の取っ手を回す以外に操作が無い。面倒が無いので頻繁にリセットできる。

欠点

  • 大きすぎず小さすぎず絶妙なサイズ感だけど、大人が使う場合は堅牢性よりも携帯性が重要な気がするので薄くしてもらった方がいいかも。オフィスでも家でも使いたい。

ポモドーロテクニックにも使えるけどそれ以外でも、分単位の締め切りのある作業をする際にバッファを持たせたタイマーを作っておき、チラチラと眺めるのはとても効果があった。やはり残り時間が面積で認識できるのは大きいと感じた。少なくなってくると、意味のない作業は切り捨てたり落とし所を考え始めるようになる。

あとは、音、操作性、視認性でストレスを全く感じないのも良い。

まぁここまでツールの話を書いてしまったけど、実際は睡眠、運動、食事、精神が全て整っていることが一番大事。その上でさらに締め切りを意識した活動が苦手ということを認識したのでこれで改善できそう、という話です。

使用感が気になる人は Android だと Visual Timer という無料アプリがあったので試してみて欲しい。

play.google.com

Time Timer 純正のアプリもあるが有料で、タイマーアプリにしては高い...

(簡易版)Google App Script で Google Spreadsheet に日本の祝日リストを吐き出す

google spread sheetに自動更新される日本の祝日シートを作る - Qiita

この Qiita のコードをもっと簡単にしてみた。

自分のバージョンはこちら。

function generateHolidayList() {
  const url = 'https://docs.google.com/spreadsheets/d/xxxxxxxx';
  const sheet = '日本の祝日';

  const start = new Date('2018-01-01T00:00:00');
  const end = new Date('2030-12-31T00:00:00');

  const holidays = CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com')
    .getEvents(start, end)
    .map(function(e) { return [e.getStartTime(), e.getTitle()] });
  SpreadsheetApp.openByUrl(url).getSheetByName(sheet).getRange(1, 1, holidays.length, 2).setValues(holidays);
}

このスクリプトは1カラム目に日付、2カラム目に祝日の名前を書き込む。

どこに書き込むのかは url, sheet 変数で決める。

一度実行すると上書きされる。実用上はそんなもんでいいのではないかな。元の qiita のは空白の制御とかもしてるけど滅多にやらないから手作業で十分だし、コードの見通しを良くしたい。あとは早すぎる抽象化をバッサリ削除した。

通常の ssh ライクな引数で簡単に EC2 にログインできるようなスクリプトを書いた

ec2ssh

Requirements

環境変数にこれ追加したほうがいいかも。

export AWS_SDK_LOAD_CONFIG=1
sudo gem install aws-sdk
wget https://gist.githubusercontent.com/ishikuro/aca4afc01f96b9c4346b6623696d88dd/raw/4acf0f135d257ade0ae88f4fa44db51abd72605f/ec2ssh
chmod u+x ec2ssh

Usage

ec2ssh [-l username] [-p port_number] [-r region] [-i identity_file] [-q] [--profile profile] [--dry-run] [username@]instance[:port_number] [command]
  • -l username or username@
    • 省略した場合は tag:User を自動的に使用する。tag:User も無いときはデフォルト値として ec2-user が使われる。
  • -p port_number or :port_number
    • 省略した場合はデフォルト値 22 が使われる
  • -r region
    • 指定したリージョンで EC2 インスタンスを探す。省略した場合は ~/.aws/configure に設定したリージョンになる。それも無ければ us-east-1 が設定される。
  • -i identity_file
    • キーペアのファイルを指定する。ssh で適切にデフォルトが設定されているならば省略してもよい。
  • -q
    • quiet mode / セキュリティグループの設定警告や実行予定の ssh コマンドを出力しない。
  • --profile profile
    • ~/.aws/configure や ~/.aws/credentials のプロファイル切り替え
  • --dry-run
    • 実際には実行しない。インスタンスのユーザ名、IP アドレスを知りたいだけのときに有用。
  • instance
    • i- で始まっていればインスタンス ID をもとに、それ以外なら EC2 インスタンスに設定した Name タグをもとに、パブリック IP アドレスを自動で取得して ssh の対象ホストとする。
  • command
    • ssh の引数として渡せる、リモートで実行したいコマンド

Examples

$ ec2ssh MyInstanceA
# => ssh ec2-user@203.0.113.1
# インスタンスの User タグがあればそちらを拾ってユーザ名にしてくれる
# User タグが無ければ ec2-user

$ ec2ssh i-xxxxxxxxxxx
# => ssh ec2-user@203.0.113.1
# i- で始まる文字列にするとインスタンス ID と見なす

$ ec2ssh ubuntu@MyInstanceB
# => ssh ubuntu@203.0.113.2
$ ec2ssh -l ubuntu MyInstanceB
# =>  ssh ubuntu@203.0.113.2
# このようにしてユーザ名は上書き可能

$ ec2ssh -r us-east-1 MyInstanceC
# => ssh centos@203.0.113.3
# リージョン指定可能

$ ec2ssh -i ./my.pem ubuntu@MyInstanceB:10022
# => ssh  -i ./my.pem -p 10022 ubuntu@203.0.113.2

$ ec2ssh -i ./my.pem -l centos -p 10022 -r us-east-1 MyInstanceC
# => ssh -i ./my.pem -p 10022 centos@203.0.113.3

おまけ機能として、デフォルトでセキュリティグループ許可し忘れがないか警告してくれる。リンク先は当該インスタンスに紐づいたセキュリティグループでフィルタされたコンソール。出力不要な場合は -q で抑止できる。

$ ec2ssh MyInstanceA
If you cannot see the login prompt, check security group settings by
https://ap-northeast-1.console.aws.amazon.com/ec2/v2/home?region=ap-northeast-1#SecurityGroups:groupId=sg-xxxxxx,sg-yyyyyy;sort=groupId

⚡️ ssh  -p 22 centos@3.xx.xx.xx

未実装

  • コマンド実行 ssh host ls みたいなやつ
  • 警告を出力しない、quietモード(-q)
  • scp モード
  • region 設定が無いときに us-east-1 を使うようにする
  • credential エラーの対応

gist.github.com

ec2ls

マネジメントコンソールを開かなくてもログインに必要な手がかりを一覧できるようなコマンドも作ってみた。併用すれば捗るはず。

ec2ls [-r region] [--profile profile] [-l]

AMI 名は先頭数文字だけ表示するようにした。ログインユーザ名を知るにはこれで十分。

$ ec2ls
i-xxxxxxxxxxxxxxxxxxx	running	CentOS#	Member
i-xxxxxxxxxxxxxxxxxxx	running	Window#	Admin
i-xxxxxxxxxxxxxxxxxxx	stopped	amzn2-#	WORK_AL2
i-xxxxxxxxxxxxxxxxxxx	running	ubuntu#	IoT
i-xxxxxxxxxxxxxxxxxxx	running	amzn-a#	Greengrass

$ ec2ls -l
i-xxxxxxxxxxxxxxxxxxx	running	CentOS#	Member	Directory/DirectoryPublic1a(192.168.0.0/24)
i-xxxxxxxxxxxxxxxxxxx	running	Window#	Admin	Directory/DirectoryPublic1a(192.168.0.0/24)
i-xxxxxxxxxxxxxxxxxxx	stopped	amzn2-#	WORK_AL2	default/DefaultPublic1a(172.31.16.0/20)
i-xxxxxxxxxxxxxxxxxxx	running	ubuntu#	IoT	default/DefaultPublic1a(172.31.16.0/20)
i-xxxxxxxxxxxxxxxxxxx	running	amzn-a#	Greengrass	default/DefaultPublic1a(172.31.16.0/20)

真面目に VPC, サブネット順にソートしたりしてるんだよ。


gist.github.com

Watch Dogs 2 ごっこができるバッグを探した

f:id:ishn:20181208101111p:plain
パルクールもこなせる鞄ちゃん

Watch Dogs 2 はハッカーをテーマにしたオープンワールドステルスアクションゲームである。

主人公マーカスはパルクールで移動しつつ必要な時に瞬時にラップトップを取り出して施設のハッキングやガジェットの操作を行うため、サイバー空間を扱っていながらも非常にテンポがいいアクションゲームになっている。


ところで私は作品に影響を受けやすいので、今回も「うおー、超かっけえ。俺もマーカスになりたい!」という感じになった(小並感)。

要件は

  • 走ってもずれない
  • 薄い
  • ラップトップだけ入ればいい

である。

あのバッグはなんなのか調査

種類はなんというのだろう。とりあえず Watch Dogs 2 のオフィシャルグッズを見つけた。

https://www.amazon.co.jp/Ubi-Workshop-Watch-2-Marcus%E3%83%A1%E3%83%83%E3%82%BB%E3%83%B3%E3%82%B8%E3%83%A3%E3%83%BC%E3%83%90%E3%83%83%E3%82%B0%E5%85%AC%E5%BC%8FUbisoft%E3%82%B3%E3%83%AC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3by-UBI%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%83%E3%83%97/dp/B071FN1DHM/ref=pd_lpo_sbs_21_t_1?_encoding=UTF8&psc=1&refRID=QACET67BKXGHD18DEBTXwww.amazon.co.jp

あー、なるほど。メッセンジャーバッグなのね。しかしゲーム中の印象に反してかなりぶ厚い。体に密着してくれるのかも怪しい… 市販のその他のメッセンジャーバッグも探してみたがこれよりもさらに大きいものが主流のようでしっくりこない。

自転車にのるときに使うサコッシュという種類のバッグが走ってもずれないし薄くて良さそうだ。しかし、サコッシュの定番は小さすぎてラップトップが入らないので決定打にならず。

サコッシュ - Google 検索

良い感じのサコッシュがあった

もうちょっと粘り強く探したらラップトップがちょうど入るサコッシュがあった。

https://this-is-not-a-store.com/

ここで売っていた Sacoche For Macbook Air というのが、私の持っている 13 inch Macbook Pro にぴったりサイズだったので注文した。

様子

f:id:ishn:20181208092332j:plain

f:id:ishn:20181208092454j:plain
これくらい入れば十分。

この装備で仕事もパフォーマンスを落とさずできるようになったので、本当にこれだけあれば困らない。MacBook Pro のバッテリーは数時間持つので、オフの日の作業だったらケーブル、充電器も不要だ。

f:id:ishn:20181208092537j:plain

f:id:ishn:20181208092632j:plain
ジップを占めるとジッパーが消えるデザイン。肩ひもの調整留め具は小さくかっちり作られていて、全く食い込まない。

f:id:ishn:20181208093003j:plain
暗くて見づらいよね。ごめんね。ちなみにもっと短くできる。

f:id:ishn:20181208093227j:plain
膝にのせてもはみ出さないので移動中も作業できる。

どうなったか

私も会津大出身の矜持として道端で急に走り出すように心がけている。今回のサコッシュはラップトップを入れても体に密着しているので、その際にも全く疲れない。

移動中もすぐに取り出せるしカフェ作業時にトイレに行くときも都度サコッシュに入れて携行できるようになった。時間が空いたときにサッと開いてCTF の問題を解くときの高揚感はまさに Watch Dogs のそれで、黒い画面をたくさん操っていると完全にスーパーハカーになれる。

上で紹介したサイトは会員制っぽいので気軽に買えないのが難点。ほかのブランドでラップトップが入るものがほとんど見つからなかったのだけど、何か知っている方いらっしゃいましたら教えてください。

Oculus Go で模様替えの計画を立てた

自分の部屋の模様替えを VR でプレビューしてみた。所要時間は休日1回分程度。想像以上に簡単で、みんなにも試して欲しいのでメモとして残しておく。

使う技術:

  • 100均で売ってるメジャー
  • DesignSpark Mechanical
  • Amazon Sumerian
  • Oculus Go

使ったお金:

  • メジャー: 108円
  • AWS 利用料金: 18円

必要な前提スキル:

  • なし

模様替え計画

部屋の様子および将来の予想図を作っておく。これには 3D モデリングツールが必要で、ラズパイでおなじみの RS Components が提供している DesignSpark Mechanical (無料)というやつが初心者に有用。Windows 専用なんだけど、持ってなければクラウドWindows サーバーでも使えばいい。

右側ペインのチュートリアルが親切で、3D モデリング未経験の私でも 30 分で何かしらの家具を作れるようになった。家具は箱型がベースだからどんどん作っていける。

とにかく家の中を 100均 で買ったメジャーで測りまくって再現した。といってもスムーズに行けば2時間かからないんじゃないかな。

f:id:ishn:20180605014618p:plain5.5畳くらいの tiny なアパートですが何か。

気が済んだら Room.obj として保存する。

f:id:ishn:20180605014630p:plain

すると、Room.mtl というのも作成される。

f:id:ishn:20180605014639p:plain

Amazon Sumerian にインポートする

Amazon Sumerian – VR アプリケーションや AR アプリケーションの構築

Amazon Sumerian ってご存知ない方も多いだろうけど、さっき作ったモデルを Oculus Go で見るために使う。習うより慣れろでとりあえずウェブのコンソールに入りましょう。

Amazon Sumerian - Pricing
AWS は有料だけど私の作成したやつの場合の今月の料金を試算したら 18円くらいだった。
  • シーンのサイズ: 14MB
  • ストレージ料金: 0.014GB * 0.06 USD = 0.00084 USD
  • 転送コスト: 0.014GB * 約30回 * 0.38 USD/GB = 0.1596 USD
  • 合計 0.16044 USD -> 18円くらい
アカウント作って1年以内の人は無料枠があるので 0 円。

AWS のコンソールは非常に多くのサービスがあって戸惑うかもしれないけど今は Sumerian にしか用はない。

f:id:ishn:20180605020434p:plainスクショには RAS症候群 が隠れていますね

入れたらおもむろに Create new scene

左下の Assets のところにさっきの Room.obj , Room.mtl二つとも一度にドラッグ&ドロップする。.mtl は色とか質感の情報が入っていて、これを一緒に入れないと全て灰色になってしまうのだが、後から個別にインポートすることができない。

f:id:ishn:20180605014644p:plain

アップロードが終わったら Assets 中の Room を中央のキャンバスエリアにまた D&D する。

するとマイホームが現れる。暗いのは光源がないから。後から入れるので大丈夫。

f:id:ishn:20180605014649p:plain

XYZ の軸が DesignSpark と異なるためひっくり返ってしまっている。左上側 Entities でさっき追加した Room が選択されていることを確認し、右側の Transform を次のように設定する。

f:id:ishn:20180605014653p:plain

中央上の Create Entity から pointlight を追加。矢印を引っ張って適当に配置。

f:id:ishn:20180605014701p:plain

VR のカメラを入れる。中央上の Import Assets から CoreVR を追加。

f:id:ishn:20180605014753p:plain

Assets にロードされたら、ツリーを辿って VRCameraRig を見つける。これをまたキャンバスに D&D 。

現時点で Entities はこんな感じになっているはず。

f:id:ishn:20180605014758p:plain

VRCameraRig を選択した状態で右側の VRCameraRig のうち CurrentCameraRig にチェックを入れる。

f:id:ishn:20180605014802p:plain

この辺りまでちょっとついて行けないという人はもう少し丁寧なステップがチュートリアルに載っているので参照してください。https://docs.sumerian.amazonaws.com/tutorials/create/beginner/getting-started-vr

VR カメラも移動する。Oculus Go を被る予定の位置にするのが没入感高くておすすめ。

f:id:ishn:20180605014824p:plain実はもうほぼ完成している。

左上のメニュー Scene -> Publish から Scene を公開する。この URL をメモしておく。

f:id:ishn:20180605014829p:plain

Oculus Go で体験する

さっきの URL をなんとかして Oculus Go に持っていく。自分は Google Keep を使った。

f:id:ishn:20180605014832p:plain

Oculus Browser で開くだけ。

f:id:ishn:20180605014835j:plain

右下のゴーグルアイコンから開始。

f:id:ishn:20180605014838j:plain

これで実際の家具を買う前に感覚を知ることができますね。すごい時代だ! f:id:ishn:20180605022318j:plain

(残念ながら Oculus Browser 経由の VR (WebVR) は録画できなかった。)