[SECCON Beginners CTF 2022] Ransom

なんか怪しいファイルと通信記録を捉えました! あれ? ここにあった超重要機密ファイルの名前が変わっているぞ...?

※ 問題のテーマからするとファイルを削除する機能があるはずですが、デバッグのしやすさのためにファイルを削除する機能は外してあります

いかにもなファイルctf4b_super_secret.txt.lockとこれまたいかにもな実行ファイルransom、そしてなんらかの通信のキャプチャであるtcpdump.pcapが渡される。

とりあえずransomをGhidraで読む。
するとだいたい以下の流れで処理をしていそうだ。

  1. 長さ0x10のASCII printableな鍵を作成
  2. ctf4b_super_secret.txtを開き内容を読み込む
  3. 暗号化する
  4. 暗号化した内容をctf4b_super_secret.txtとして出力する
  5. 鍵を192.168.0.255:8080に送信

とりあえずパケットキャプチャから鍵を取り出す。
鍵はただのソケット通信で送られており暗号化などはされていないため16文字のASCII printableなデータがないかをかたっぱしから探していけば割とすぐ見つかった。

次に暗号化の処理を見ていく。

undefined8 encrypt(char *seed,char *plain,byte *encrypted_size)

{
  long in_FS_OFFSET;
  byte key [264];
  long local_10;

  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  genkey(seed,key);
  enc(key,plain,encrypted_size);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

undefined8 genkey(char *seed,byte *key)

{
  uint uVar1;
  size_t seedlen;
  int iVar2;
  int local_18;
  int local_14;
  int local_10;

  seedlen = strlen(seed);
  local_18 = 0;
  for (local_14 = 0; local_14 < 0x100; local_14 = local_14 + 1) {
    key[local_14] = (byte)local_14;
  }
  for (local_10 = 0; local_10 < 0x100; local_10 = local_10 + 1) {
    iVar2 = (uint)key[local_10] + local_18 + (int)seed[local_10 % (int)seedlen];
    uVar1 = (uint)(iVar2 >> 0x1f) >> 0x18;
    local_18 = (iVar2 + uVar1 & 0xff) - uVar1;
    swap(key + local_10,key + local_18);
  }
  return 0;
}

undefined8 enc(byte *key,char *plain,byte *encrypted)

{
  size_t sVar1;
  uint local_24;
  uint local_20;
  long local_18;

  local_24 = 0;
  local_20 = 0;
  local_18 = 0;
  sVar1 = strlen(plain);
  for (; (ulong)local_18 < sVar1; local_18 = local_18 + 1) {
    local_24 = local_24 + 1 & 0xff;
    local_20 = key[(int)local_24] + local_20 & 0xff;
    swap(key + (int)local_24,key + (int)local_20);
    encrypted[local_18] = plain[local_18] ^ key[(byte)(key[(int)local_20] + key[(int)local_24])];
  }
  return 0;
}

まず最初に生成した鍵を実際に暗号化に用いる鍵に変換する処理genkeyが行われ、その後実際に暗号化する処理encが実行されるという2ステップによって暗号化を行われている。
encの中では鍵の順番を入れ替えたりはしているものの基本的にはただのxor暗号なので、暗号化の処理をもう一度行えば平文に戻る。

ということで、暗号化関数をそのままPythonに移植してソルバを仕上げた。

#!/usr/bin/env python3
KEY_SEED = b"rgUAvvyfyApNPEYg"

def genkey(seed, key):
  seedlen = len(seed)
  local_18 = 0
  for i in range(0x100):
    key[i] = i & 0xff

  for i in range(0x100):
    iVar2 = key[i] + local_18 + seed[i % seedlen]
    uVar1 = (iVar2 >> 0x1f) >> 0x18
    local_18 = (iVar2 + uVar1 & 0xff) - uVar1
    key[i], key[local_18] = key[local_18], key[i]

  return key

def enc(key, plain, encrypted):
  local_24 = 0
  local_20 = 0
  #local_18 = 0
  sVar1 = len(plain)
  for local_18 in range(sVar1):
    local_24 = local_24 + 1 & 0xff
    local_20 = key[local_24] + local_20 & 0xff
    key[local_24] ,key[local_20] = key[local_20], key[local_24]
    encrypted[local_18] = plain[local_18] ^ key[(key[local_20] + key[local_24]) & 0xff]
  return encrypted

def encrypt(seed, plain, encrypted):
  key = [0] * 256
  key = genkey(seed,key)
  enc(key,plain,encrypted)

with open('ctf4b_super_secret.txt.lock', 'r') as f:
  locked = f.read()

locked_raw = eval("b'" + locked + "'")

key = genkey(KEY_SEED, [0] * 256)
flag = enc(key, locked_raw, [0] * 256)
print(''.join(map(chr, flag)))

ctf4b{rans0mw4re_1s_v4ry_dan9er0u3_s0_b4_c4refu1}

みなさまランサムウェアには気をつけましょう