Sionの技術ブログ

SREとして日々の学習を書いて行きます。twitterは@sion_cojp

プロセスの実装と生成・終了ってどうなってるの?





プロセスってどう管理されてるの?

  • プロセステーブルにプロセスが起動するたびにエントリが追加、削除されてる




プロセステーブルのエントリには何が書かれてるの?

  • C言語のstructとして定義されてることが多い(プロセス構造体)
  • 主に下記3つ
  • ①プロセス管理情報
    • レジスタの退避領域:プロセス切替えの時レジスタ(PCやSP)を退避するための領域
    • プロセスの属性:プロセスIDや所有者など
    • 統計情報:プロセスが動いた時間
  • ②メモリ管理情報(コード、データ、スタック領域)
  • ③ファイル管理情報(オープンしたファイルの情報)




プロセスの生成の流れ

  1. プロセステーブル内の空き領域を探す
  2. プロセス構造体を割り当てる
  3. プロセス構造体の初期化(プロセスID決め、データ・スタック・コード領域の大きさを決め割り当て)
  4. プロセス実行




プロセス終了の流れ

  1. オープンしてたファイルをすべて閉じる
  2. プロセスが使用してたメモリ領域の解放
  3. プロセス構造体を解放
  4. プロセステーブルのエントリ解放
  5. プロセスの終了

プロセスって何?





はじめに




そもそもOSは

f:id:sion_cojp:20170223172326p:plain:w500

  • OSではマルチタスク機能によって、細かくプロセスを切り替えることで"同時に動かしてる様に見せる"
  • 並列ではなく並行。
  • Webブラウザやメーラーなど複数アプリを実行してるときマルチタスク機能を使っている。




プロセスって何?

  • 実行状態にあるプログラム
  • アプリは(複数の)プロセスから作られてる
  • CPU(プロセッサ)は1度に1つのプロセスしか処理できない




プロセスの動き

f:id:sion_cojp:20170228164344p:plain:w500

  • プロセスは実行準備、実行、待ちの3種類状態がある
  • プロセスは常に1つのプログラムしか実行出来ないので、うまく切り替えてる。




プロセスの切り替えってどうやってるの?

  • 切り替える手法は、プリエンプティブ方式と、ノンプリエンプティブ方式がある




プリエンプティブ方式と、ノンプリエンプティブ方式って何?

  • プリエンプティブ:OSがプロセッサの実行権限を管理して、切り替える。現在のOSはこれ。
  • ノンプリエンプティブ:プログラム自身に任せる。昔のOS。
    • ちなみにgoroutineはノンプリエンプティブ方式を使って切り替えてる




プロセスの実行スケジュール管理はどうやってるの?

  • OSによって色々ある
  • 例えば、ラウンドロビン方式:実行可能状態にあるプロセスを順番に実行す




プロセスとメモリの関係

  • CPUは、指定したプログラムコードがある場所(アドレス)にジャンプする命令を持っている
  • アドレスはメモリ 1byte
  • CPUのビット数でアドレスの数が決まる
    • 32bit 232 = 0xFFFFFFFF ≒ 4GByte
    • 64bit 264 = 0xFFFFFFFFFFFFFFFF = 16EByte ≒ 172億GByte
    • までのアドレスを認識出来る(0xは16進数って意味)
  • 誰も使っていないアドレスを探して利用する必要があるので、MMU(memory managemanet unit)がアドレス管理している
  • CPUがプロセスを実行する際、そのプロセスが持っているアドレスに対して演算を行う




演算って何?

  • 四則演算(足す、引く、かける、割る)
  • 論理演算(AND・OR・NOT)
  • など




MMUって何?

f:id:sion_cojp:20170223172335p:plain:w500

  • プロセス毎に物理メモリ領域を確保->その領域にアクセスするための仮想アドレスを用意する
    • 同一OS内のすべてのプロセスは同一サイズの仮想アドレスを持つ
    • CPUの中にMMUと仮想アドレスがある。メモリ上にはない
    • プロセスが「メモリを使用する」宣言すると確保される
  • プロセス->仮想アドレス->物理アドレス




仮想アドレスのメリットは?

f:id:sion_cojp:20170327020132p:plain:w500

  • 他のプロセスからアクセスできないように制御されてるので、セキュア
  • 物理メモリはバラバラでも、仮想アドレスは連続した領域のように扱えるため、メモリの隙間も有効に使える
  • 図のように、同じ物理メモリをマッピングすることで、複数のプロセスでメモリを共有できる
    • 共有ライブラリのロードとか




複数のプロセスが共有してるデータにアクセスすると競合(レースコンディション)しない?

  • もし、あるプロセスが共有メモリを読んでいるなら、 他のプロセスは「書きこみ終了するのを待つ 機構」を作る必要があるよね
  • UNIXでは、そのようなプロセス間通信(IPC)に下記手法があるよ
  • 例えばセマフォ排他制御
    • 0,1の値を保持しておき、0の場合はアクセスNG、1の場合はアクセスOKなど。
    • 解放出来なかった(この場合だとずっと1にならない)場合、デッドロックって言うよ
  • ちなみにGolangでは「メモリ共有より、メッセージ共有」を推奨している




プロセスがメモリに保持するデータってどんなの?

f:id:sion_cojp:20170327020230p:plain:w500

  • 上位アドレスはCPUのビット数と、メモリの数によって変わる
    • 32bit OSだったら、232 = 0xFFFFFFFF = 4GB(先ほども出ましたね)
  • 大きく3つに分かれる
  • ①テキストセグメント
    • 実行されるプログラム。
  • ②データセグメント
    • 静的領域:定数、グローバル変数など。データ(初期化ありとBSS(初期化なし))がある
    • ヒープ:malloc()などで動的に確保。GCでクリア。
  • ③スタックセグメント
    • スタック:CPUのレジスタを一時的に退避させたりする。ローカル変数を確保。上位アドレス側には引数と環境変数が置かれる。




スタックって何?

  • 処理中のデータを一時的に退避(入れたり出したり)するデータ構造
  • 最後に入った要素が最初に取り出されるのでLIFOともいう(Last-In, First-Out)
  • 同じデータ構造のキューは最初に入った要素が最初に取り出されるのでFIFO(First-In, First-Out)という
空のスタック             [
Aをスタックに入れる       [A
Bをスタックに入れる       [A B
Bをスタックから取り出す    [A
]




malloc()って何?

  • memoryをallocate(確保)するための関数
  • 必要な量を必要なときに動的にヒープ領域に確保できるのがメリット
  • 通信プログラムだと、クライアントが接続してきたときに、メッセージ交換に使うメッセージ用のバッファを動的に確保したり。
  • クライアントが切断したら、free()で解放できるので、柔軟にメモリが使える。




スタック領域とヒープ領域

  • どちらも実行時にメモリを確保する領域
ヒープ スタック
領域確保 動的
実行時確保
静的
コンパイル時確保
解放 手動 自動
容量
速度




GCって何?

  • ヒープ領域がいっぱいになると新しいオブジェクトがロード出来なくてプログラムが実行できない
  • GCは、ヒープ領域に空きがなくなると、使用されてないオブジェクトを判別してメモリ上から消去する




スタックメモリって何?

  • タスクや、関数内だけで使われる変数やアドレス情報を保持するメモリ領域
  • タスクを終了する or 関数から抜けると、スタックの内容も破棄される
  • この中にスタックを構築し、下記を保持していく。

    • スタックポインタ (SP): スタック領域のどこを見ているかを指す値
    • プログラムカウンタ (PC): プログラムのどこを実行しているかを指す値
  • ちなみにGolangでは、スタックメモリが不足すると、新しいチャンクが確保されて追加されるので、スタックオーバーフロー(スタックにメモリを積み過ぎて死ぬ)は発生しにくい




Golangでスタックオーバーフローを起こすには?

package main

import "fmt"

func main() {
    var a []interface{}
    a = make([]interface{}, 0, 10)
    a = append(a,
        append(a, 1))
    fmt.Println(a)
}
// runtime: goroutine stack exceeds 250000000-byte limit
// fatal error: stack overflow
// ......

インフラエンジニアの綺麗で優しい手順書の書き方

書きました。 手順書って書くの難しいし、面倒くさいですよね。 綺麗でわかりやすい手順を書いても、会社に評価してもらえなかったりとかも原因だと思ってます。 けどちゃんと書けば会社や皆さんのためになるので、是非ご参考下さい。

リブセンスに勤めて2年間を振り返って





前職(某DC:3年ちょっと : サーバエンジニア)

  • サーバとネットワーク機器のハードウェア周りをメインに、キッティング、Firmware, Bios, IOSの* アップデートや初期化や障害対応
  • サーバとネットワーク機器の開梱、マウント、ケーブリング、OS初期構築
  • 色んなサーバのベンチマークテスト
  • DNSの運用
  • エッジルータの運用
  • 顧客のL3, L2スイッチ、Juniper FWの構築
  • ioDriveの運用。
  • 社内ツールを色々とPerlで作成(ioDriveのベンチマークとか。)

現職(リブセンス : 2年(7月入社) : インフラエンジニア)

1年目まで
  • 保守期限切れのため、大量のサーバリプレイス
  • 新たなサーバ選定、購入
  • 物理、論理ネットワーク構成図作成
  • L3,L2の設定見直し、適用
  • 実運用されてる全てのサーバのリバースエンジニアリング(構築、運用、再起動手順作成)
  • vyatta→vyos移行
  • メール配信サーバのスケールアウト
  • 手順をいっぱい作成する毎日
  • メディアに所属し、keepalive, modroxy, nginx, apache, mysql, mysqld_multi, NAT, memcache などなどの構築、運用。
  • DNSレコードの整理
  • 稼働率を集計するアプリをrailsで作成
  • 開発環境と本番環境の完全分離
  • nagiosとmunin

  • qiitaで一番buzzたのは"インフラエンジニアとしてよく使うコマンド集"(http://qiita.com/sion_cojp/items/04a2aa76a1021fe77079

    1年目までを振り返って
  • 勝手がわからなかった部分もあってあたふたしてた時代。
  • エンジニア用語難しい
  • 社内用語難しい
  • Linuxはかなり知識不足で入社しましたが、実際に運用をしてみて多くの知識を得た。
  • 入社前にapache + nginx周りをガリガリ触ってたことで結構助けられた。
  • 会話の中で聞いた単語をメモって調べての繰り返し。他人のソースコードをみて調べての繰り返し。それで力が付いた。
  • 今でもやれてるのは素敵。
  • nagiosでアラートが上がった時、何もできない悔しさをバネに障害に積極参加「どうやって復旧したんですか?」と聞いたり
  • かなり良い経験でした。
  • railsとか触ったことないけどなんとか2週間で出来た。
2年目まで
  • 社内DNS構築、運用
  • apacheとnginxの最新版のベンチマークhttp://qiita.com/sion_cojp/items/edb20a6b87f10e186c23
  • OpenStackの検証と開発環境に導入(Controller, Network, Compute, Cinder)
  • knife-solo(chef)を使ったOS構築自動化
  • packer + chefを使ったvagrant box作成
  • ansibleで各環境に置かれたサーバに対してコマンドが発行できるようにした

  • qiitaで一番buzzったのは “CentOS6とCentOS7の比較表"(http://qiita.com/sion_cojp/items/115e1671fcbc8f214aee

    2年目までを振り返って
  • 会社に慣れてきたこともあって、スピード感持って攻める姿勢に。
  • なんでもできる と思ってきたのはこの頃。
  • 一時的に人数少なくなったけど、目標設定は変えない頑固さとスピード感。そして達成。
  • スクラムによるタスク管理と、2週間毎の振り返りでかなり良くなったと思ってます。
  • 特にタスク毎にPDCA回してタスク完了スピードをもっと速く。
  • 障害対応も速くなった。
  • 曖昧な部分はなくし、自動化促進。結果色んな技術に触れることが出来ました。
  • メディアの人たち巻き込んだり、色々調整したり。
  • これおかしい、と思ったら即修正いれたり。
  • OpenStackの検証に手こずって、頭悩ましてた時期。OpenStackIRCに突撃もしたり。OpenStackの実運用tipsってネットに載ってないことが多かったりする。
  • 開発環境に導入出来てよかった
  • qiita記事よくbuzzってた
  • defconの問題を初めて解いた。かなり手探りだったけど楽しかった!
  • MVPノミネート1回、受賞1回と良い

    今後の豊富

  • Goでプラグイン書いてます。どんどん書いてoutputを出したい
  • consulも触っていきます。自動化万歳
  • 詳解カーネルを読んでカーネルにもっと詳しく。
  • defconの問題をもっと解きたい
  • アプリ作ってみたい。
  • これからもスピード感は持っていきたい
  • 2016年には1度は社外発表したい。します。
  • 次こそはMVPとります

VolgaCTF 2015 : 100 - poemのwriteup

手探り

### ファイルを落として、調べてみる
$ wget http://files.2015.volgactf.ru/poem/poem.pdf
 
### ファイル形式でだまされないように、ちゃんと確認。
$ file poem.pdf
poem.pdf: PDF document, version 1.5

ちゃんとpdfでした。

中身を見てみる

行間が怪しい。バーコードか。ってことは2進数か。ASCII変換か。
改行してないものを0、改行を1として調べて行く。

f:id:sion_cojp:20150514200830j:plain

地味な作業。
なんか途中で9bit部分が出て来た。ページじゃなくて全体としてビット数を数えていくっぽい。
とりあえずhttp://d.hatena.ne.jp/kobapan/20081225/1230156733 に当てはめて行く

{siz

まで読めたが、次から読めなかった。
文末か文頭にビットが入るのかな?
文末に0が入る事にしてみると、全て8ビットに入れ替わったページをまたぐ部分は米印を入れている

{ 01111011
s 01110011
i 01101001 
z 01111010
e *01100101
_ 01011111
d 01100100
o 01101111
% *00100101
s 01110011
_ 01011111
m 01101101
A *01000001
t 01110100
t 01110100
e 01100101
r *01110010
_ 01011111
a 01100001
f 01100110
p *01110000
e 01100101
r 01110010
_ 01011111
` *01100000
l 01101100
l 01101100
~ 01111110

うむ。{で始まってるのに、}で終わってない。
最後は01111101になるべきだ。ということは文末は0or1で正しく出力出来た方か。
というかこの段階でなんとなくキーは予想出来そう笑

完成品

ASCII 2進数
{ 01111011
s 01110011
i 01101001
z 01111010
e ページまたぎ 01100101
_ 01011111
d 01100100
o 01101111
e ページまたぎ  01100101
s 01110011
_ 01011111
m 01101101
a ページまたぎ 01100001
t 01110100
t 01110100
e 01100101
r ページまたぎ 01111000
_ 01011111
a 01100001
f 01100110
t ページまたぎ 01110100
e 01100101
r 01110010
_ 01011111
a ページまたぎ 01100001
l 01101100
l 01101100
} 01111111

答え

{size_does_matter_after_all}

VolgaCTF 2015 : 75 - databaseのwriteup

手探り

### databaseに接続してみる
$ nc database.2015.volgactf.ru 7777
>> help
Unknown command or incorrect number of parameteres.
>> pwd
Unknown command or incorrect number of parameteres.

### ダウンロードしたヒントファイルを調べてみる
$ file database
database: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, not stripped

Linux 64bit形式だと言う事が分かる。

### 中身を見てみる
$ more database
"database" may be a binary file.  See it anyway?

バイナリか・・・

### とりあえず表示出来る文字列だけ表示してみる
$ strings database
# 怪しいコマンドはこんなところか。
admin
get_flag
whoami
login
register
get_info
set_info
get_flag_prohibited
get_flag
 
 
 
### 怪しいところをdatabaseに打ち込んでみる
>> admin
Unknown command or incorrect number of parameteres.
>> get_flag
This command is prohibited to non-admin users
>> whoami
Unknown command or incorrect number of parameteres.
>> login
Unknown command or incorrect number of parameteres.
>> register
Unknown command or incorrect number of parameteres.
>> get_info
admin : {it_is_not_the_flag!}
>> set_info
Unknown command or incorrect number of parameteres.
>> get_flag_prohibited
Unknown command or incorrect number of parameteres.
>> get_flag
This command is prohibited to non-admin users.


adminになれば色々コマンドが打てる事が分かった。
どうすればadminになれるか。
adminを登録すれば良いのか。

アセンブラ

$ sudo objdump -d database
0000000000401436 <register_user>:
  401436:   55                      push   %rbp
  401437:   48 89 e5                mov    %rsp,%rbp
  40143a:   48 83 ec 30             sub    $0x30,%rsp
  40143e:   89 7d ec                mov    %edi,-0x14(%rbp)
  401441:   48 89 75 e0             mov    %rsi,-0x20(%rbp)
  401445:   48 89 55 d8             mov    %rdx,-0x28(%rbp)
  401449:   48 89 4d d0             mov    %rcx,-0x30(%rbp)
  40144d:   48 83 7d d8 00          cmpq   $0x0,-0x28(%rbp)
  401452:   74 07                   je     40145b <register_user+0x25>
  401454:   48 83 7d d0 00          cmpq   $0x0,-0x30(%rbp)
  401459:   75 2a                   jne    401485 <register_user+0x4f>
  40145b:   48 8b 05 2e 1d 20 00    mov    0x201d2e(%rip),%rax        # 603190 <no_data>
  401462:   48 89 c7                mov    %rax,%rdi
  401465:   e8 d6 f9 ff ff          callq  400e40 <strlen@plt>
  40146a:   48 89 c2                mov    %rax,%rdx
  40146d:   48 8b 35 1c 1d 20 00    mov    0x201d1c(%rip),%rsi        # 603190 <no_data>
  401474:   8b 45 ec                mov    -0x14(%rbp),%eax
  401477:   b9 00 00 00 00          mov    $0x0,%ecx
  40147c:   89 c7                   mov    %eax,%edi
  40147e:   e8 ed f9 ff ff          callq  400e70 <send@plt>
  401483:   eb 6a                   jmp    4014ef <register_user+0xb9>
  401485:   48 8b 05 2c 1d 20 00    mov    0x201d2c(%rip),%rax        # 6031b8 <users>
  40148c:   48 8b 55 d8             mov    -0x28(%rbp),%rdx
  401490:   48 89 d6                mov    %rdx,%rsi
  401493:   48 89 c7                mov    %rax,%rdi
  401496:   e8 95 f9 ff ff          callq  400e30 <g_hash_table_lookup@plt>
  40149b:   48 89 45 f8             mov    %rax,-0x8(%rbp)
  40149f:   48 83 7d f8 00          cmpq   $0x0,-0x8(%rbp)
  4014a4:   74 2a                   je     4014d0 <register_user+0x9a>
  4014a6:   48 8b 05 cb 1c 20 00    mov    0x201ccb(%rip),%rax        # 603178 <user_exists>
  4014ad:   48 89 c7                mov    %rax,%rdi
  4014b0:   e8 8b f9 ff ff          callq  400e40 <strlen@plt>
  4014b5:   48 89 c2                mov    %rax,%rdx
  4014b8:   48 8b 35 b9 1c 20 00    mov    0x201cb9(%rip),%rsi        # 603178 <user_exists>
  4014bf:   8b 45 ec                mov    -0x14(%rbp),%eax
  4014c2:   b9 00 00 00 00          mov    $0x0,%ecx
  4014c7:   89 c7                   mov    %eax,%edi
  4014c9:   e8 a2 f9 ff ff          callq  400e70 <send@plt>
  4014ce:   eb 1f                   jmp    4014ef <register_user+0xb9>
  4014d0:   48 8b 4d d0             mov    -0x30(%rbp),%rcx
  4014d4:   48 8b 45 d8             mov    -0x28(%rbp),%rax
  4014d8:   ba 00 00 00 00          mov    $0x0,%edx
  4014dd:   48 89 ce                mov    %rcx,%rsi
  4014e0:   48 89 c7                mov    %rax,%rdi
  4014e3:   e8 8c fc ff ff          callq  401174 <insert_new_user>
  4014e8:   48 8b 55 e0             mov    -0x20(%rbp),%rdx
  4014ec:   48 89 02                mov    %rax,(%rdx)
  4014ef:   c9                      leaveq
  4014f0:   c3                      retq

eとかrとかめちゃくちゃになってるのは仕方なし。
rdi = 0x14(20)
rsi = 0x20(32)
rdx = 0x28(40)
rcx = 0x30(48)

$0x0,-0x28(rdx = 0x0(null))の場合、もしくは$0x0,-0x30(rcx = 0x0(null))の場合、

strlen関数持って来て、rdi = 0x14(20)の文字列の長さをno_dataポインタに格納 + raxに返り値を入れる。
って一個ずつやってもいいんですが、IDAproやhopper使うと見易くなります。

Hopperで解析

f:id:sion_cojp:20150514034042p:plain

registerコマンドしっかりありました。

/* 先ほどのregister_user functionを逆アセンブラ */
function register_user {
    var_28 = rdi;
    var_16 = rsi;
    var_8 = rdx;
    var_0 = rcx;
    if ((var_8 == 0x0) || (var_0 == 0x0)) {
            rax = strlen(*no_data);
            rax = send(var_28, *no_data, rax, 0x0);
    }
    else {
            rax = g_hash_table_lookup(*users, var_8, var_8);
            var_40 = rax;
            if (var_40 != 0x0) {
                    rax = strlen(*user_exists);
                    rax = send(var_28, *user_exists, rax, 0x0);
            }
            else {
                    rax = insert_new_user(var_8, var_0, 0x0, var_0);
                    *var_16 = rax;
            }
    }
    return rax;
}

さっきより見やすい。
全体の流れはこんな感じ。

①var_28, 16, 8, 0を各レジスタに格納。
②もし、var_8か、var_0がnull(0x0)の場合、
・文字列の長さ(整数)をno_dataポインタに格納。(raxレジスタに返り値を入れる)
・var_28(ソケットディスクリプタ)を使って、no_dataポインタに格納された文字列の長さの値を送る。
③それ以外の場合、
・var_8(引数1。ユーザ名)var_8(引数2。パスワード)をusersポインタに格納

③-1 もし、var_40がnull(0x0)じゃ無い場合、
・文字列の長さ(整数)をuser_exitsポインタに格納。
・var_28(ソケットディスクリプタ)を使って、user_exitsポインタに格納された文字列の長さの値を送る。
③-2 それ以外は、
・insert_new_user functionより、新しいユーザを作る

つまり、入力ユーザが指定されてない場合と、入力ユーザが指定されてる場合は、①入力ユーザが既に存在しない場合②入力ユーザが存在してない場合
に分かれる。

/* insert_new_user function */

function insert_new_user {
    var_24 = rdi;
    var_16 = rsi;
    var_8 = rdx;
    rax = calloc(0x40, 0x1);
    var_32 = rax;
    rax = rtrim(var_24);
    strncpy(var_32, rax, 0x40);
    *(int8_t *)(var_32 + 0x40) = 0x0;
    rax = calloc(0x80, 0x1);
    var_40 = rax;
    rax = rtrim(var_16);
    strncpy(var_40, rax, 0x40);
    *(int8_t *)(var_40 + 0x40) = 0x0;
    if (var_8 != 0x0) {
            rax = rtrim(var_8);
            strncpy(var_40 + 0x40, rax, 0x40);
            *(int8_t *)(var_40 + 0x80) = 0x0;
    }
    rax = g_hash_table_insert(*users, var_32, var_40, var_32);
    return var_32;
}

うーむ。idapro欲しい。

もし入力ユーザが存在してる場合は、rtrimを使って削除して、strncpy(文字列をn文字コピー) で上書きする動きなのは分かる。

/* rtrim */
function rtrim {
    var_8 = rdi;
    rax = strlen(var_8);
    var_24 = var_8 + rax + 0xffffffffffffffff;
loc_401164:
    if (var_24 >= var_8) goto loc_401126;
    goto loc_40116e;
loc_401126:
    if (((*(int8_t *)var_24 & 0xff) != 0xd) && ((*(int8_t *)var_24 & 0xff) != 0xa)) goto loc_40113c;
    goto loc_401158;
loc_40113c:
    if (((*(int8_t *)var_24 & 0xff) != 0x20) && ((*(int8_t *)var_24 & 0xff) != 0x9)) goto loc_401152;
    goto loc_401158;
loc_401152:
    rax = var_8;
loc_401172:
    return rax;
loc_401158:
    *(int8_t *)var_24 = 0x0;
    var_24 = var_24 - 0x1;
    goto loc_401164;
loc_40116e:
    rax = var_8;
    goto loc_401172;
}

loc_401126:で0xdと0xa(改行コード)を無視している
loc_40113c:で0x20と0x9(スペースとタブ)を無視している
http://ratan.dyndns.info/MicrosoftVisualC++/eskape.html



ということは、第一引数のユーザ名で、
adminの後に改行コード(inuxだとctrl+v, ctrl+mで出来る^M。CR。)か、tabコード(LF。0xA。普通にタブを押せば入る)を入れれば、新しいユーザとして認識され、パスワードを上書きしてしまう脆弱性っぽい

ちなみにスペースだと引数が増えるだけなのでだめ。

実際にやってみる

$ nc database.2015.volgactf.ru 7777
>> whoami
You are not even logged in!
>> register admin    a
>> whoami
You are admin.
>> get_flag
flag: {does_it_look_like_column_tr@ncation}

答え

{does_it_look_like_column_tr@ncation}