basic-file-exploit
picoCTF 2022の「basic-file-exploit」。テキトーに色々な入力をしていたらフラグが出てきた。
この問題はpwnというよりもrevの問題(cのソースコードだけ配布される)。
「どんな入力をしてフラグを得たのか」→「なぜその入力で得られたのか」の順にソースコードを読み解いていく。
プログラムの概要
shoebill@pwner:~/pico$ ./conn.sh Hi, welcome to my echo chamber! Type '1' to enter a phrase into our database Type '2' to echo a phrase in our database Type '3' to exit the program 1 1 Please enter your data: test test Please enter the length of your data: 4 4 Your entry number is: 1 Write successful, would you like to do anything else? 2 2 Please enter the entry number of your data: 1 1 test Read successful, would you like to do anything else?
1
でデータ入力+データの長さを入力、2
でエントリーナンバーを指定してデータを読み出す。
フラグを得られた入力
適当に1
を実行し後に次のように2
を実行したらいきなりフラグが出てきた:
2 2 Please enter the entry number of your data: %p %p picoCTF{M4K3_5UR3_70_CH3CK_Y0UR_1NPU75_E0394EC0}
ソースを読み解く
2
番ではdata_read()
関数が実行されている:
static void data_read() { char entry[4]; long entry_number; char output[100]; int r; memset(output, '\0', 100); printf("Please enter the entry number of your data:\n"); r = tgetinput(entry, 4); // Timeout on user input if(r == -3) { printf("Goodbye!\n"); exit(0); } if ((entry_number = strtol(entry, NULL, 10)) == 0) { puts(flag); fseek(stdin, 0, SEEK_END); exit(0); } entry_number--; strncpy(output, data[entry_number], input_lengths[entry_number]); puts(output); }
つまり次を実行させた:
if ((entry_number = strtol(entry, NULL, 10)) == 0) { puts(flag); fseek(stdin, 0, SEEK_END); exit(0); }
strtol(const char *s, char **endptr, int base);
は文字列をlong値に変換する関数。
(strtol
のリファレンス)
const char *s
: 変換対象文字列char **endptr
: 変換不可能な文字列へのポインタの格納先
(NULLの場合には、変換不可能な文字列への処理は行わない)int base
: 基数
戻り値:
- 成功時:
s
の値をlongで返却 - 失敗時: 0
strtol(entry, NULL, 10))
を失敗させるには?
strtol(entry, NULL, 10)) == 0
となるすなわちstrtol
を失敗させればputs(flag)
されるから、strtol
を失敗させればよい。
そのためには、10進変換できないものを入力してやればよい。
strtol
のリファレンスにもあるように、ABCDEFは10進変換できないからそれを与えてもフラグを得られる:
2 2 Please enter the entry number of your data: ABCDEF ABCDEF picoCTF{M4K3_5UR3_70_CH3CK_Y0UR_1NPU75_E0394EC0}
ちなみにentry
はどこから来たかのかというと...
static void data_read() { char entry[4]; long entry_number; char output[100]; int r; memset(output, '\0', 100); printf("Please enter the entry number of your data:\n"); r = tgetinput(entry, 4); // Timeout on user input if(r == -3) { printf("Goodbye!\n"); exit(0); } if ((entry_number = strtol(entry, NULL, 10)) == 0) { ...
さらにtargetinput
を辿ると、その第一引数であるinput
はこちら側が入力する値とわかる:
int tgetinput(char *input, unsigned int l) { ... if (ready_for_reading) { read_bytes = read(0, input, l-1); if(input[read_bytes-1]=='\n'){ --read_bytes; input[read_bytes]='\0'; } if(read_bytes==0){ printf("No data given.\n"); return -4; } else { return 0; } } else { printf("Timed out waiting for user input. Press Ctrl-C to disconnect\n"); return -3; } return 0; ...