Beginners CTF 2019 が 2019/5/25 ~ 5/26 で開催されていたので参加しました!
相変わらず点数の低い問題しか解いていませんが、備忘録も兼ねてwrite-up書いておきます。あとで復習記事も書いておきたい。
今回は8問解いて897pt, 114位 (1問以上解いた666チーム中) でした。内訳はこんな感じ。
解いた時間が細切れなのは、育児タスクの合間にちょこちょこやったから…。こうやってみるとわかりやすい。
CTFを始めて身についた知識を使いました!!!と大きな声で言えるものが殆どないのが残念。Cryptoあたりもう1問くらい解きたかったなぁ。
順序は適当。手を付けた順です。
- [Misc] [warmup] Welcome
- [Reversing] [warmup] Seccompare
- [Web] [warmup] Ramen
- [Web] katsudon
- [Crypto] [warmup] So Tired
- [Crypto] Party
- [Misc] containers
- [Misc] Sliding puzzle
- 感想
ちなみに去年のBeginners参加write-up。
warmup4問 + Misc1問だったので、少しは進歩した様子。
[Misc] [warmup] Welcome
SECCON Beginners CTFのIRCチャンネルで会いましょう。
IRC: freenode.net #seccon-beginners-ctf
IRCに入ると、チャネルの説明にflagが。
[Reversing] [warmup] Seccompare
https://score.beginners.seccon.jp/files/seccompare_44d43f6a4d247e65c712d7379157d6a9.tar.gz
サイトにアクセスしてファイルをDLします。
こんなファイルが出現しました。
$ file seccompare seccompare: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux
実行してみます。
$ ./seccompare usage: ./seccompare flag
ということで、引数が一つ必要なようです。
# ./seccompare flag wrong
こんな感じ。違ったら違うと言ってくれるようです。
radare2で解析してみようとmain関数を見たら、flagが書いてありました。
| |`-> 0x00400630 c645d063 mov byte [local_30h], 0x63 ; 'c' ; 99 | | 0x00400634 c645d174 mov byte [local_2fh], 0x74 ; 't' ; 116 | | 0x00400638 c645d266 mov byte [local_2eh], 0x66 ; 'f' ; 102 | | 0x0040063c c645d334 mov byte [local_2dh], 0x34 ; '4' ; 52 | | 0x00400640 c645d462 mov byte [local_2ch], 0x62 ; 'b' ; 98 | | 0x00400644 c645d57b mov byte [local_2bh], 0x7b ; '{' ; 123 | | 0x00400648 c645d635 mov byte [local_2ah], 0x35 ; '5' ; 53 | | 0x0040064c c645d774 mov byte [local_29h], 0x74 ; 't' ; 116 | | 0x00400650 c645d872 mov byte [local_28h], 0x72 ; 'r' ; 114 | | 0x00400654 c645d931 mov byte [local_27h], 0x31 ; '1' ; 49 | | 0x00400658 c645da6e mov byte [local_26h], 0x6e ; 'n' ; 110 | | 0x0040065c c645db67 mov byte [local_25h], 0x67 ; 'g' ; 103 | | 0x00400660 c645dc73 mov byte [local_24h], 0x73 ; 's' ; 115 | | 0x00400664 c645dd5f mov byte [local_23h], 0x5f ; '_' ; 95 | | 0x00400668 c645de31 mov byte [local_22h], 0x31 ; '1' ; 49 | | 0x0040066c c645df73 mov byte [local_21h], 0x73 ; 's' ; 115 | | 0x00400670 c645e05f mov byte [local_20h], 0x5f ; '_' ; 95 | | 0x00400674 c645e16e mov byte [local_1fh], 0x6e ; 'n' ; 110 | | 0x00400678 c645e230 mov byte [local_1eh], 0x30 ; '0' ; 48 | | 0x0040067c c645e374 mov byte [local_1dh], 0x74 ; 't' ; 116 | | 0x00400680 c645e45f mov byte [local_1ch], 0x5f ; '_' ; 95 | | 0x00400684 c645e565 mov byte [local_1bh], 0x65 ; 'e' ; 101 | | 0x00400688 c645e66e mov byte [local_1ah], 0x6e ; 'n' ; 110 | | 0x0040068c c645e730 mov byte [local_19h], 0x30 ; '0' ; 48 | | 0x00400690 c645e875 mov byte [local_18h], 0x75 ; 'u' ; 117 | | 0x00400694 c645e967 mov byte [local_17h], 0x67 ; 'g' ; 103 | | 0x00400698 c645ea68 mov byte [local_16h], 0x68 ; 'h' ; 104 | | 0x0040069c c645eb7d mov byte [local_15h], 0x7d ; '}' ; 125
flag: ctf4b{5tr1ngs_1s_n0t_en0ugh}
[Web] [warmup] Ramen
こんなページが。
なんか入力フォームと SEARCH
ボタンが有るので、すでにいるっぽい「せくこん太郎」を入れてSEARCHしてみる
HITしました。
つぎに、admin'--
などのSQL injection用クエリを投げてみます。
エラーページ現る。シングルクォーテーション'
が入ってるともうだめっぽい。
今回はcookieも無いことから、検索機能・DB機能系の脆弱性を疑います。
ここで、検索欄に せ
とだけ入れてみたところ、せくこん太郎・次郎・三郎の3人が表示されました。
…ということは前方一致でしょうか?ちなみに く
でも出てきました。前方どころじゃないようです。
ここで心当たりのある文字を総当たりで入力してみましたが、他に店員DBにはいないようでHitしません。
0-9
,a-z
,A-Z
,あ-ん
,ア-ン
くらいを試してみたんですけどね・・・。
warmupだし、店員DBにFlag君がいて、一言にflagが書いてあることを期待していました。。。
ここで SQL injection の過去問をあさっていると下記の記事が。
CTF for ビギナーズ 2016 FINAL @東京 Writeup - Qiita
2016年のBeginnersのfinal問題です。
この手順に沿ってやってみたら出来ました。最初から SQL injection のクエリの組み立て方がわかっていたらいきなり最後のクエリを勘で組み立てて解けた気がします…。
まず、SQL injection が刺さっているかどうかの確認です。下記のように入れると全レコードがエラー無く出てきました。
' OR 1=1 --
今回も最後にスペースが必要でした。
次に、テーブルの列数を調べます。nullを増やしていくと2つでエラーでなかったので、テーブルは2列。
' union select null, null --
欲しいtable_name
とcolum_name
を2列に表示するようなクエリを組み立てます。
' union select table_name, column_name from information_schema.columns --
名前 一言 せくこん太郎 1970 年よりラーメン道一本。美味しいラメーンを作ることが生きがい。 せくこん次郎 せくこん太郎の弟。好きな食べものはコッペパン。 せくこん三郎 せくこん次郎の弟。食材本来の味を引き出すことに全力を注ぐ。 CHARACTER_SETS CHARACTER_SET_NAME CHARACTER_SETS DEFAULT_COLLATE_NAME CHARACTER_SETS DESCRIPTION (中略) INNODB_FT_CONFIG KEY INNODB_FT_CONFIG VALUE flag flag members username members profile
たくさん出てきました。flag flag
がいます。テーブル・カラム名の特定は、勘でいった人のほうが多そう。
最後に、flag
テーブルのflag
カラムを抜き出して表示してもらいます。
' union select flag, null from flag --
取れました〜!!!٩(๑❛ᴗ❛๑)۶ ٩(๑❛ᴗ❛๑)۶ ٩(๑❛ᴗ❛๑)۶
今回もWeb問全然解けない、しかもwarmupすら解けないところだったので、終了間際に解けてめっちゃ嬉しかった!
[Web] katsudon
Rails 5.2.1で作られたサイトです。
https://katsudon.quals.beginners.seccon.jp
クーポンコードを復号するコードは以下の通りですが、まだ実装されてないようです。
フラグは以下にあります。 https://katsudon.quals.beginners.seccon.jp/flag
# app/controllers/coupon_controller.rb class CouponController < ApplicationController def index end def show serial_code = params[:serial_code] @coupon_id = Rails.application.message_verifier(:coupon).verify(serial_code) end end
なんか問題文にソースが書いてあります。
指定のurlに行くとこんなページ。topが店舗一覧画面のようです。
他、クーポンゲット = シリアルコードを入力するページがあります。
ためしに店舗一覧ページに合ったシリアルコードをクーポンゲットページに入力してみました。
ええー!
ちなみにどのクーポンも使えませんでした。
ここで問題文にあった、cupon_controller.rb
を見てみます。
これが実装されていないので、まだクーポンが使えないとのこと。
ちなみに、flag用のシリアル番号は
BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU--0def7fcd357f759fe8da819edd081a3a73b6052a
のようです。(問題文のリンクより)
なので、未実装の cupon_controller
の動作を理解して上記シリアル番号を解析、クーポンをゲットできればそこにflagがありそう。ありそう。
ここでもう一度、既に与えられているクーポンを確認します。
- 令和飯店
- ソースカツ丼の名店
- シリアルコード:
BAhJIhByZWl3YWhhbnRlbgY6BkVU--bc5614afcef948624ebc137432c2dcdc624111b6
- 平成食堂
- 持ち帰りが可能
- シリアルコード:
BAhJIhNoZWlzZWlzaG9rdWRvdQY6BkVU--f9aa81191fb073fb87bfa71b20c02bf3a30d1b10
- レストラン昭和
- デリバリー可能
- シリアルコード:
BAhJIhRyZXN0YXVyYW50c2hvd2EGOgZFVA==--a78497e11151cffc45af945a1a243138b6084140
何やらそれぞれ先頭が base64 encode されてるっぽいので、試しに --
の前を base64 decodeしてみます。
- 令和飯店
I"reiwahanten:ET
- 平成食堂
I"heiseishokudou:ET
- レストラン昭和
I"restaurantshowa:ET
- flag
I"%ctf4b{K33P_Y0UR_53CR37_K3Y_B453}:ET
なぁぁにぃぃぃぃっ!もうここでflagが出てきてしまった…!正攻法かわかりませんが、通ったので良し。ソースコード何も使わなかった…。
[Crypto] [warmup] So Tired
最強の暗号を作りました。 暗号よくわからないけどきっと大丈夫!
tarファイルが落とせたので解凍すると、encrypted.txt
が出現。超長い。
$ file encrypted.txt encrypted.txt: ASCII text, with very long lines, with no line terminators
まずはこのなかにflagがないかを確認。
$ grep 'ctf4b{' encrypted.txt
なし。
ぱっと見た感じ、base64のような文字列なので、base64 decodeしてみる。
更に、base64 decode結果を見たところ、バイナリファイルだったので書き出してみました。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- import base64 with open('encrypted.txt') as f: data = f.read() decoded = base64.b64decode(data) with open('dat', 'wb') as f: f.write(decoded)
書き出したファイルを調べてみます。
$ file dat dat: zlib compressed data
zlibでした。更にこのファイルをdecompressしてやると、今度はまたbase64っぽい文字列。
なるほど。これは base64 - zlib のルーっプッぽいぞ、ということで、汚いですが下記のコードを。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- import base64 import zlib with open('encrypted.txt') as f: data = f.read() while True: decoded = base64.b64decode(data) with open('dat', 'wb') as f: f.write(decoded) data = zlib.decompress(decoded) with open('decompressed', 'wb') as f: f.write(data)
base64 decode か zlib decompress ができなくなったらエラーで止まるはずなので、止まったら成果物のファイルの中身を確認しようという作戦。
意図通り(?)エラーで止まったので、成果物ファイルの一つdecompressed
の中身を見ると、flagがありました。
$ cat decompressed ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}
[Crypto] Party
Let's 暗号パーティ
落としたtarファイルを解凍すると、pythonコードとテキストファイルが。
encrypt.py
from flag import FLAG from Crypto.Util.number import bytes_to_long, getRandomInteger, getPrime def f(x, coeff): y = 0 for i in range(len(coeff)): y += coeff[i] * pow(x, i) return y N = 512 M = 3 secret = bytes_to_long(FLAG) assert(secret < 2**N) coeff = [secret] + [getRandomInteger(N) for i in range(M-1)] party = [getRandomInteger(N) for i in range(M)] val = map(lambda x: f(x, coeff), party) output = list(zip(party, val)) print(output)
encrypted
[(5100090496682565208825623434336918311864447624450952089752237720911276820495717484390023008022927770468262348522176083674815520433075299744011857887705787, 222638290427721156440609599834544835128160823091076225790070665084076715023297095195684276322931921148857141465170916344422315100980924624012693522150607074944043048564215929798729234427365374901697953272928546220688006218875942373216634654077464666167179276898397564097622636986101121187280281132230947805911792158826522348799847505076755936308255744454313483999276893076685632006604872057110505842966189961880510223366337320981324768295629831215770023881406933), (3084167692493508694370768656017593556897608397019882419874114526720613431299295063010916541874875224502547262257703456540809557381959085686435851695644473, 81417930808196073362113286771400172654343924897160732604367319504584434535742174505598230276807701733034198071146409460616109362911964089058325415946974601249986915787912876210507003930105868259455525880086344632637548921395439909280293255987594999511137797363950241518786018566983048842381134109258365351677883243296407495683472736151029476826049882308535335861496696382332499282956993259186298172080816198388461095039401628146034873832017491510944472269823075), (6308915880693983347537927034524726131444757600419531883747894372607630008404089949147423643207810234587371577335307857430456574490695233644960831655305379, 340685435384242111115333109687836854530859658515630412783515558593040637299676541210584027783029893125205091269452871160681117842281189602329407745329377925190556698633612278160369887385384944667644544397208574141409261779557109115742154052888418348808295172970976981851274238712282570481976858098814974211286989340942877781878912310809143844879640698027153722820609760752132963102408740130995110184113587954553302086618746425020532522148193032252721003579780125)]
初めて知ったんですけど、coeff
って多項式係数の抽出に使われる関数らしい。これを踏まえてencrypt.py
を眺めてみると、ただの3連立方程式に落とせそう。
関数 f(x, coeff)
が、
y = coeff[0] + coeff[1]*x + coeff[2]*x^2
なので、
val[i] = coeff[0] + coeff[1]*party[i] + coeff[2]*(party[i]**2)
※ただし i =0~2
となります。coeff[0]
が求まれば、これがflagです。
numpyのlinalg
やscipy
など、色々python用のモジュールやスクリプトを使って楽して方程式を解いてもらう方法を試してみましたが、桁が大きすぎてそのまま使えるものが見つからず。
仕方ないので、下の数式を参考に、普通にcoeff[0]
を計算する式の計算をゴリゴリ書きました。
Python (SymPy) で方程式・連立方程式を解く、数列を求める - pianofisica
#!/usr/bin/env python3 # -*- coding:utf-8 -*- from Crypto.Util.number import long_to_bytes party = [5100090496682565208825623434336918311864447624450952089752237720911276820495717484390023008022927770468262348522176083674815520433075299744011857887705787, 3084167692493508694370768656017593556897608397019882419874114526720613431299295063010916541874875224502547262257703456540809557381959085686435851695644473, 6308915880693983347537927034524726131444757600419531883747894372607630008404089949147423643207810234587371577335307857430456574490695233644960831655305379] val = [222638290427721156440609599834544835128160823091076225790070665084076715023297095195684276322931921148857141465170916344422315100980924624012693522150607074944043048564215929798729234427365374901697953272928546220688006218875942373216634654077464666167179276898397564097622636986101121187280281132230947805911792158826522348799847505076755936308255744454313483999276893076685632006604872057110505842966189961880510223366337320981324768295629831215770023881406933, 81417930808196073362113286771400172654343924897160732604367319504584434535742174505598230276807701733034198071146409460616109362911964089058325415946974601249986915787912876210507003930105868259455525880086344632637548921395439909280293255987594999511137797363950241518786018566983048842381134109258365351677883243296407495683472736151029476826049882308535335861496696382332499282956993259186298172080816198388461095039401628146034873832017491510944472269823075, 340685435384242111115333109687836854530859658515630412783515558593040637299676541210584027783029893125205091269452871160681117842281189602329407745329377925190556698633612278160369887385384944667644544397208574141409261779557109115742154052888418348808295172970976981851274238712282570481976858098814974211286989340942877781878912310809143844879640698027153722820609760752132963102408740130995110184113587954553302086618746425020532522148193032252721003579780125] # val[i] = coeff[0] + coeff[1]*party[i] + coeff[2]*(party[i]**2) # flag is coeff[0] A = [[1, party[0], party[0]*party[0]], [1, party[1], party[1]*party[1]], [1, party[2], party[2]*party[2]]] b = val # refer to follow page. # https://pianofisica.hatenablog.com/entry/2019/04/04/233515#%EF%BC%93%E5%A4%89%E6%95%B0%E3%81%AE%E5%A0%B4%E5%90%88 d = A[0][1]*(A[2][2]*b[1]-A[1][2]*b[2]) + \ A[0][2]*(A[1][1]*b[2]-A[2][1]*b[1]) + \ b[0]*(A[1][2]*A[2][1]-A[1][1]*A[2][2]) n = A[0][0]*(A[1][2]*A[2][1]-A[1][1]*A[2][2]) + \ A[0][1]*(A[1][0]*A[2][2]-A[1][2]*A[2][0]) + \ A[0][2]*(A[1][1]*A[2][0]-A[1][0]*A[2][1]) ans = d//n print(long_to_bytes(ans))
コードが汚すぎてワケワカメですが…。参考リンクの方を参照して下さいまし。
実行結果。
$ python solve.py b'ctf4b{just_d0ing_sh4mir} '
[Misc] containers
Let's extract files from the container. https://score.beginners.seccon.jp/files/e35860e49ca3fa367e456207ebc9ff2f_containers
urlにアクセスすると、ファイルが貰えました。
$ file e35860e49ca3fa367e456207ebc9ff2f_containers e35860e49ca3fa367e456207ebc9ff2f_containers: data
stringコマンドで見てみると、下記のような気になる情報が書いてあります。(抜粋)
$ strings e35860e49ca3fa367e456207ebc9ff2f_containers CONTAINER.FILE0. IHDR sRGB gAMA pHYs aIDATx^ ajlK-[ 0TG~5 ;-M\ IEND FILE1. IHDR sRGB gAMA pHYs (中略) VALIDATOR.import hashlib print('Valid flag.' if hashlib.sha1(input('Please your flag:').encode('utf-8')).hexdigest()=='3c90b7f38d3c200d8e6312fbea35668bec61d282' else 'wrong.'.ENDCONTAINER
画像っぽい文言が最初の方にあり、最後に急にソースっぽい文言が…?
とりあえず foremost
かけてみると、沢山のpngイメージが出現。一文字ずつ書いてあり、ファイル名順に並べるとflagになっていました!
$ foremost -i e35860e49ca3fa367e456207ebc9ff2f_containers Processing: e35860e49ca3fa367e456207ebc9ff2f_containers |*|
flag: ctf4b{e52df60c058746a66e4ac4f34db6fc81}
[Misc] Sliding puzzle
nc 133.242.50.201 24912
スライドパズルを解いてください。すべてのパズルを解き終わったとき FLAG が表示されます。
スライドパズルは以下のように表示されます。
----------------
| 0 | 2 | 3 |
| 6 | 7 | 1 |
| 8 | 4 | 5 |
----------------
0 はブランクで動かすことが可能です。操作方法は以下のとおりです。
0 : 上
1 : 右
2 : 下
3 : 左
最終的に以下の形になるように操作してください。
----------------
| 0 | 1 | 2 |
| 3 | 4 | 5 |
| 6 | 7 | 8 |
----------------
操作手順は以下の形式で送信してください。
1,3,2,0, ... ,2
なんだか競プロとかpaizaの問題っぽい。
スライドパズルを解くアルゴリズムの紹介はたくさんありましたが、時間勝負なので、できればpythonのスクリプトが落ちていることを願ってgithubを漁ります。
運良く落ちてました!!
しかも 3×3 に特化したやつです。
動作環境がpython2なので、問題をとってきて配列にするところと、出力がグラフィカルな表示だったのをブランクを動かす手順に変えるところだけ、エイヤで書いて通しました。
#!/usr/bin/env python2 # -*- coding:utf-8 -*- # This code uses bellow script # https://github.com/fabianokafor369/Sliding-puzzle-solver from pwn import * host = '133.242.50.201' port = 24912 SHIFT_U = 0 SHIFT_R = 1 SHIFT_D = 2 SHIFT_L = 3 # ---- https://github.com/fabianokafor369/Sliding-puzzle-solver # ---- start reference class PuzzleNode: (略) def place_heuristic(state): (略) def Manhattan_heuristic(state): (略) def myheuristic(state): (略) #Creating list heuristics which is a pointer to both heuristics created heuristics = [place_heuristic, Manhattan_heuristic, myheuristic] def goalstate(state): (略) def moves(inputs, n): (略) def Astar(start, finish, heuristic): (略) def solvePuzzle(n, state, heuristic, prnt): (略) #Prints the solutions if the prnt = True #Here I change code from https://github.com/fabianokafor369/Sliding-puzzle-solver if prnt == True: shift = [] pre_blank_pos = {} blank_pos = {} for n in range(len(solutions[1:])): for i in range(len(solutions[n+1])): if solutions[n+1][i] == '0': break blank_pos = calc_pos(i) if n != 0: shift.append(str(calc_shift(pre_blank_pos, blank_pos))) pre_blank_pos = blank_pos return ','.join(shift) # ---- end reference """ BLANK_POS 02 05 08 13 16 19 24 27 30 """ def calc_pos(pos): BLANK_POS = [[2, 5, 8], [13, 16, 19], [24, 27, 30]] for i in range(3): for j in range(3): if pos == BLANK_POS[i][j]: return [i, j] def calc_shift(pre, curr): if curr[0] - pre[0] == 0: if curr[1] - pre[1] == 0: raise Exception if curr[1] - pre[1] == 1: return SHIFT_R if curr[1] - pre[1] == -1: return SHIFT_L elif curr[0] - pre[0] == 1: return SHIFT_D elif curr[0] - pre[0] == -1: return SHIFT_U r = remote(host, port) print(r.recvline().strip()) while True: p = r.recvuntil('\n\n') print p.strip() p = p.split('\n') puzzle = [[] for i in range(3)] for i in range(3): for j in range(3): puzzle[i].append(int(p[i].split('|')[j+1].strip())) print 'puzzle array: ' print puzzle ans = solvePuzzle(3, puzzle, heuristics[2], True) print 'answer: ' + ans r.sendline(ans) print r.recvline().strip()
実行結果
(前略) ---------------- | 03 | 01 | 02 | | 07 | 06 | 05 | | 04 | 08 | 00 | ---------------- puzzle array: [[3, 1, 2], [7, 6, 5], [4, 8, 0]] answer: 3,3,0,1,2,3,0,0 ---------------- | 03 | 00 | 05 | | 04 | 02 | 01 | | 06 | 07 | 08 | ---------------- puzzle array: [[3, 0, 5], [4, 2, 1], [6, 7, 8]] answer: 2,1,0,3,2,3,0 [+] Congratulations! ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}
どうやら100問あったみたいです!
感想
前回の感想
土日で耐久24時間って、用事があったり子守があったりで結局使える時間頑張っても6時間くらい。それでも今の実力だと十分だった気がするし、長くやりすぎても飽きそうなのでちょうどよかったかな。
同じこと書こうとしてたよ。危ない危ない。ただ今回はもっと時間欲しかったので、やっぱり少しは前進したかな。
Twitterでは愚痴ばっかりになってしまいましたが、子育て世代、特に育児メイン担当の週末CTFイベント参加は厳しいものがありますね。いっそ合宿とかで家にいないようにしてパートナーや他の方に育児を丸投げしないと育児イベントがちょこちょこ割り込むので、中々集中できる時間をまとまって確保するのが難しかったです。うまくやってる方いたらお話を聞きたい…!
まぁ今回はそれ以前に夫に急に仕事が入り、一日丸々子供と二人だったのですが( •̅_•̅ ) オンライン競技って理解してもらうのも難しいなぁと再認識。
SECCON for Beginners、本日15:00~18:00は無事参戦できた。お風呂上がりからちょっと旦那に子供を任せて参戦しようとしてたんだけど「おかーちゃんがいーいー!!!」と泣き叫んでいる…(˘•ω•˘)
— kusuwada@産前休暇中 (@kusuwada) 2019年5月25日
明日は旦那いないし、せめてラーメンは解きたい…。#ctf4b
子供にお昼ご飯を食べさせながら「ラリホー!!!ラリホー!!!」と唱えまくる日曜日(失敗)
— kusuwada@産前休暇中 (@kusuwada) 2019年5月26日
オンライン競技 with ワンオペ子守あるある。うまくいけば子供のお昼寝中に競技に参加できます。失敗した場合は・・・白目(꒪ཀ꒪)ちなみにラリホーマは未実装です…。早く覚えたい…。
ちなみに娘は競技が終了して1時間後に無事お昼寝をはじめました(๑′௰‵๑) なんだかんだ可愛いのです。