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 !!
$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 において
- is_numeric が true
- 文字列長が1から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=®ister-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=®ister-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
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 で調べると、"ゼロ幅スペースステガノグラフィ"に関連するツールを見つけた。
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 が最適だった。
- 面積で残り時間を視認できる
- 単機能
- アラームを切れる
- 時計のチクタク音が無い
もともと子供やASDなどもスコープに入れたツールであり押さえるべきところが押さえられている。
欠点は価格だ。安いモデルでも5,000円以上する。Time Timer 自体は学生の頃から知っていたが高すぎて手が出なかった。自作も検討していたがずっと放置していた。
忘れていたころに、類似の IoT デバイスで Kotobo という、30分タイマーを通知するだけのボタンが登場した。とても欲しいと思ったがこれはなんと 12,980円もする。
これを買う前に類似の機能を持ったより安価なもので効果を知っておきたいなと思い、ふと Time Timer を思い出して Kotobo の半値以下なら買ってしまうか、と決断したのである。
Unboxing
インテリアに馴染むような小型モデルで、Time Timer MOD というのがあり 5,000円ちょっとで買った。確か昔は買える場所が少なかったような気がするが、今では楽天の玩具屋などでも扱っている。
外箱はおしゃれ。
内容物はこれ。
本体は柔らかいシリコンカバーがついていて雑に扱ってもいいようになっている。このカバーにカラーバリエーションがあるらしく、それで MOD というらしい。
背面はアラーム音のスイッチと電池蓋がある。電池は同梱されておらず、単三電池1本と蓋を開けるためのメガネ用ドライバーが必要。蓋、ネジは固めなので、小さい子供に渡しても問題なさそうだ。
文字盤中央の取っ手を回すだけで自動でカウントダウンが始まる。アラーム音はこんな感じで4回鳴って終了。ずっと鳴り続けたりはしないのが素晴らしい。
使用感
良い
- 赤い面積で残り時間が直感的にわかる。これを求めていた。
- 中央の取っ手を回す以外に操作が無い。面倒が無いので頻繁にリセットできる。
欠点
- 大きすぎず小さすぎず絶妙なサイズ感だけど、大人が使う場合は堅牢性よりも携帯性が重要な気がするので薄くしてもらった方がいいかも。オフィスでも家でも使いたい。
ポモドーロテクニックにも使えるけどそれ以外でも、分単位の締め切りのある作業をする際にバッファを持たせたタイマーを作っておき、チラチラと眺めるのはとても効果があった。やはり残り時間が面積で認識できるのは大きいと感じた。少なくなってくると、意味のない作業は切り捨てたり落とし所を考え始めるようになる。
あとは、音、操作性、視認性でストレスを全く感じないのも良い。
まぁここまでツールの話を書いてしまったけど、実際は睡眠、運動、食事、精神が全て整っていることが一番大事。その上でさらに締め切りを意識した活動が苦手ということを認識したのでこれで改善できそう、という話です。
使用感が気になる人は Android だと Visual Timer という無料アプリがあったので試してみて欲しい。
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 変数で決める。
- url は書き込み対象のスプレッドシート 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
- -i identity_file
- キーペアのファイルを指定する。ssh で適切にデフォルトが設定されているならば省略してもよい。
- -q
- quiet mode / セキュリティグループの設定警告や実行予定の ssh コマンドを出力しない。
- --profile profile
- --dry-run
- 実際には実行しない。インスタンスのユーザ名、IP アドレスを知りたいだけのときに有用。
- instance
- 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 エラーの対応
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, サブネット順にソートしたりしてるんだよ。
Watch Dogs 2 ごっこができるバッグを探した
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
あー、なるほど。メッセンジャーバッグなのね。しかしゲーム中の印象に反してかなりぶ厚い。体に密着してくれるのかも怪しい… 市販のその他のメッセンジャーバッグも探してみたがこれよりもさらに大きいものが主流のようでしっくりこない。
自転車にのるときに使うサコッシュという種類のバッグが走ってもずれないし薄くて良さそうだ。しかし、サコッシュの定番は小さすぎてラップトップが入らないので決定打にならず。
良い感じのサコッシュがあった
もうちょっと粘り強く探したらラップトップがちょうど入るサコッシュがあった。
https://this-is-not-a-store.com/
ここで売っていた Sacoche For Macbook Air というのが、私の持っている 13 inch Macbook Pro にぴったりサイズだったので注文した。
様子
この装備で仕事もパフォーマンスを落とさずできるようになったので、本当にこれだけあれば困らない。MacBook Pro のバッテリーは数時間持つので、オフの日の作業だったらケーブル、充電器も不要だ。
どうなったか
婚活市場で東工大についでFランと間違われやすい国立大学が電気通信大学です。どちらも理系大学で就職先は男ばかりの職場、服がダサい、急に走り出すなどの特徴を持つ人が多く完全にキャラ被りをしています。
— ひかりん@婚活コンサル (@hikarin22) December 2, 2018
そしてこれに会津大学を加えた3大学を婚活界の非モテ御三家と呼びます。
私も会津大出身の矜持として道端で急に走り出すように心がけている。今回のサコッシュはラップトップを入れても体に密着しているので、その際にも全く疲れない。
移動中もすぐに取り出せるしカフェ作業時にトイレに行くときも都度サコッシュに入れて携行できるようになった。時間が空いたときにサッと開いてCTF の問題を解くときの高揚感はまさに Watch Dogs のそれで、黒い画面をたくさん操っていると完全にスーパーハカーになれる。
上で紹介したサイトは会員制っぽいので気軽に買えないのが難点。ほかのブランドでラップトップが入るものがほとんど見つからなかったのだけど、何か知っている方いらっしゃいましたら教えてください。
Oculus Go で模様替えの計画を立てた
自分の部屋の模様替えを VR でプレビューしてみた。所要時間は休日1回分程度。想像以上に簡単で、みんなにも試して欲しいのでメモとして残しておく。
DesignSpark Mechanical で DIY 構想を練って、部屋の間取りを再現したところで、Amazon Sumerian に食わせれば Oculus Go で模様替えシミュレートできるじゃん!と気づいた。個人でもこういうことできる時代すごいわ。 pic.twitter.com/qg5fsIdpWH
— ishikuro (@ishi_kuro) 2018年6月3日
使う技術:
- 100均で売ってるメジャー
- DesignSpark Mechanical
- Amazon Sumerian
- Oculus Go
使ったお金:
- メジャー: 108円
- AWS 利用料金: 18円
必要な前提スキル:
- なし
模様替え計画
部屋の様子および将来の予想図を作っておく。これには 3D モデリングツールが必要で、ラズパイでおなじみの RS Components が提供している DesignSpark Mechanical (無料)というやつが初心者に有用。Windows 専用なんだけど、持ってなければクラウドの Windows サーバーでも使えばいい。
右側ペインのチュートリアルが親切で、3D モデリング未経験の私でも 30 分で何かしらの家具を作れるようになった。家具は箱型がベースだからどんどん作っていける。
とにかく家の中を 100均 で買ったメジャーで測りまくって再現した。といってもスムーズに行けば2時間かからないんじゃないかな。
5.5畳くらいの tiny なアパートですが何か。
気が済んだら Room.obj
として保存する。
すると、Room.mtl
というのも作成される。
Amazon Sumerian にインポートする
Amazon Sumerian – VR アプリケーションや AR アプリケーションの構築
Amazon Sumerian ってご存知ない方も多いだろうけど、さっき作ったモデルを Oculus Go で見るために使う。習うより慣れろでとりあえずウェブのコンソールに入りましょう。
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円くらい
AWS のコンソールは非常に多くのサービスがあって戸惑うかもしれないけど今は Sumerian にしか用はない。
スクショには RAS症候群 が隠れていますね
入れたらおもむろに Create new scene
左下の Assets のところにさっきの Room.obj
, Room.mtl
を二つとも一度にドラッグ&ドロップする。.mtl
は色とか質感の情報が入っていて、これを一緒に入れないと全て灰色になってしまうのだが、後から個別にインポートすることができない。
アップロードが終わったら Assets 中の Room を中央のキャンバスエリアにまた D&D する。
するとマイホームが現れる。暗いのは光源がないから。後から入れるので大丈夫。
XYZ の軸が DesignSpark と異なるためひっくり返ってしまっている。左上側 Entities でさっき追加した Room が選択されていることを確認し、右側の Transform を次のように設定する。
中央上の Create Entity から pointlight を追加。矢印を引っ張って適当に配置。
VR のカメラを入れる。中央上の Import Assets から CoreVR を追加。
Assets にロードされたら、ツリーを辿って VRCameraRig を見つける。これをまたキャンバスに D&D 。
現時点で Entities はこんな感じになっているはず。
VRCameraRig を選択した状態で右側の VRCameraRig のうち CurrentCameraRig にチェックを入れる。
この辺りまでちょっとついて行けないという人はもう少し丁寧なステップがチュートリアルに載っているので参照してください。https://docs.sumerian.amazonaws.com/tutorials/create/beginner/getting-started-vr
VR カメラも移動する。Oculus Go を被る予定の位置にするのが没入感高くておすすめ。
実はもうほぼ完成している。
左上のメニュー Scene -> Publish から Scene を公開する。この URL をメモしておく。
Oculus Go で体験する
さっきの URL をなんとかして Oculus Go に持っていく。自分は Google Keep を使った。
Oculus Browser で開くだけ。
右下のゴーグルアイコンから開始。
これで実際の家具を買う前に感覚を知ることができますね。すごい時代だ!
(残念ながら Oculus Browser 経由の VR (WebVR) は録画できなかった。)