💫

SECCON Beginners CTF 2025作問者Writeup(Reversing)

に公開

JUCKです。ご縁がありましてReversingの作問をさせていただきました
(ありがとうございます...!)
ここでは

  • CrazyLazyProgram1
  • CrazyLazyProgram2
  • D-compile
  • MAFC
    の4問を解説していきます

CrazyLazyProgram1(beginner)

添付ファイルを開くとC#形式のプログラムが与えられます。

using System;class Program {static void Main() {int len=0x23;Console.Write("INPUT > ");string flag=Console.ReadLine();if((flag.Length)!=len){Console.WriteLine("WRONG!");}else{if(flag[0]==0x63&&flag[1]==0x74&&flag[2]==0x66&&flag[3]==0x34&&flag[4]==0x62&&flag[5]==0x7b&&flag[6]==0x31&&flag[7]==0x5f&&flag[8]==0x31&&flag[9]==0x69&&flag[10]==0x6e&&flag[11]==0x33&&flag[12]==0x72&&flag[13]==0x35&&flag[14]==0x5f&&flag[15]==0x6d&&flag[16]==0x61&&flag[17]==0x6b&&flag[18]==0x33&&flag[19]==0x5f&&flag[20]==0x50&&flag[21]==0x47&&flag[22]==0x5f&&flag[23]==0x68&&flag[24]==0x61&&flag[25]==0x72&&flag[26]==0x64&&flag[27]==0x5f&&flag[28]==0x32&&flag[29]==0x5f&&flag[30]==0x72&&flag[31]==0x33&&flag[32]==0x61&&flag[33]==0x64&&flag[34]==0x7d){Console.WriteLine("YES!!!\nThis is Flag :)");}else{Console.WriteLine("WRONG!");}}}}

35文字のflagという配列を定義し、1文字ずつ確認していくプログラムとなっています。

solution

flag = [0x63, 0x74, 0x66, 0x34, 0x62, 0x7b, 0x31, 0x5f, 0x31, 0x69, 0x6e, 0x33, 0x72, 0x35, 0x5f, 0x6d,
        0x61, 0x6b, 0x33, 0x5f, 0x50, 0x47, 0x5f, 0x68, 0x61, 0x72, 0x64,
        0x5f, 0x32, 0x5f, 0x72, 0x33, 0x61, 0x64, 0x7d]

back=""

for c in flag:
    back += chr(c)

print(back)
ctf4b{1_1in3r5_mak3_PG_hard_2_r3ad}

CrazyLazyProgram2(easy)

.o拡張子のオブジェクトファイルが添付されています
gccを使うことでELFファイルにしてくれるのでまずはファイル変換を行います

gcc ./CLP2.o

コンパイルによって出てきた./a.outを実行するとflagの入力を求められます。
出てきたELFをお好みのデコンパイルツールに投げてみましょう(僕はGhidraを使用しました)
解析結果を見ていくと以下のような長いifが出てきます

...
  if (((((((((local_38 == 'c') && (local_c = 1, cStack_37 == 't')) &&
           (local_c = 2, cStack_36 == 'f')) &&
          (((local_c = 3, cStack_35 == '4' && (local _c = 4, cStack_34 == 'b')) &&
           ((local_c = 5, cStack_33 == '{' &&
...

始めのcは除き、local_cがインデックス、cStack_XXが文字であることが分かります。

solution

local38='c'
cstack=['t', 'f', '4', 'b','{' ,'G', 'O', 'T', '0', '_', 'G', '0', 'T', '0', '_', '9', '0', 't', '0', '_', 'N', '0', 'm', '0', 'r', '3', '_', '9', '0', 't', '0','}']
back=""+local38

for c in cstack:
    back += c

print(back)
ctf4b{GOTO_G0T0_90t0_N0m0r3_90t0}

D-compile(easy)

Ghidraでデコンパイルすると上手くいかないのでGDBを使用して解析する必要があります
disass mainで確認すると_Dmainが呼び出されており、中身を確認すると以下のような処理があります

   0x0000000000003382 <+40>:    call   0x3170 <_d_arrayliteralTX@plt>
   0x0000000000003387 <+45>:    movb   $0x63,-0x50(%rbp)
   0x000000000000338b <+49>:    movb   $0x74,-0x4f(%rbp)
   0x000000000000338f <+53>:    movb   $0x66,-0x4e(%rbp)
   0x0000000000003393 <+57>:    movb   $0x34,-0x4d(%rbp)
   0x0000000000003397 <+61>:    movb   $0x62,-0x4c(%rbp)
   0x000000000000339b <+65>:    movb   $0x7b,-0x4b(%rbp)
   0x000000000000339f <+69>:    movb   $0x4e,-0x4a(%rbp)
    ...

このmovbの第一引数に注目すると0x63(='s')であることが分かるため、この配列にflag文字が格納されていると分かります。

solution

chars=[0x63,0x74,0x66,0x34,0x62,0x7b,0x4e,0x33,0x78,0x74,0x5f,0x54,0x72,0x33,0x6e,0x64,0x5f,0x44,0x5f,0x31,0x61,0x6e,0x39,0x75,0x61,0x67,0x33,0x5f,0x31,0x30,0x31,0x7d];
flag=""
for c in chars:
    flag+=chr(c)

print(flag)
ctf4b{N3xt_Tr3nd_D_1an9uag3_101}

MAFC(hard)

MalwareAnalysis-FirstChallenge.exeによって暗号化されたflagを復元する問題です
ghidraを用いてデコンパイルすると以下のようなプログラムが出てきます

void FUN_1400011a0(void)

{
  uint uVar1;
  code *pcVar2;
  longlong lVar3;
  BOOL BVar4;
  DWORD nNumberOfBytesToRead;
  HANDLE hFile;
  HANDLE hFile_00;
  void *pvVar5;
  longlong lVar6;
  BYTE *pbData;
  BYTE *_Memory;
  ulonglong _Size;
  longlong lVar7;
  BYTE *pBVar8;
  undefined1 auStackY_b8 [32];
  HCRYPTKEY local_78;
  HCRYPTPROV local_70;
  BYTE local_68 [8];
  DWORD local_60;
  DWORD local_5c;
  HCRYPTHASH local_58;
  BYTE local_50 [24];
  ulonglong local_38;
  
  local_38 = DAT_140005000 ^ (ulonglong)auStackY_b8;
  pbData = (BYTE *)0x0;
  hFile = CreateFileA("flag.txt",0x80000000,1,(LPSECURITY_ATTRIBUTES)0x0,3,0x80,(HANDLE)0x0);
  if (hFile == (HANDLE)0xffffffffffffffff) {
    puts("Failed to handle flag.txt\n");
  }
  else {
    hFile_00 = CreateFileA("flag.encrypted",0x40000000,0,(LPSECURITY_ATTRIBUTES)0x0,2,0x80,
                           (HANDLE)0x0);
    if (hFile_00 == (HANDLE)0xffffffffffffffff) {
      puts("Failed to handle flag.encrypted\n");
      goto LAB_140001637;
    }
    BVar4 = CryptAcquireContextW
                      (&local_70,(LPCWSTR)0x0,
                       L"Microsoft Enhanced RSA and AES Cryptographic Provider",0x18,0);
    if ((BVar4 == 0) &&
       (BVar4 = CryptAcquireContextW
                          (&local_70,(LPCWSTR)0x0,
                           L"Microsoft Enhanced RSA and AES Cryptographic Provider",0x18,8),
       BVar4 == 0)) {
      puts("CryptAcquireContext() Error\n");
      goto LAB_140001637;
    }
    BVar4 = CryptCreateHash(local_70,0x800c,0,0,&local_58);
    if (BVar4 == 0) {
      puts("CryptCreateHash() Error\n");
      goto LAB_140001637;
    }
    builtin_memcpy(local_50 + 0x10,"Key",4);
    builtin_memcpy(local_50,"ThisIsTheEncrypt",0x10);
    lVar6 = -1;
    do {
      lVar7 = lVar6 + 1;
      lVar3 = lVar6 + 1;
      lVar6 = lVar7;
    } while (local_50[lVar3] != '\0');
    BVar4 = CryptHashData(local_58,local_50,(DWORD)lVar7,0);
    if (BVar4 == 0) {
      puts("CryptHashData() Error\n");
      goto LAB_140001637;
    }
    BVar4 = CryptDeriveKey(local_70,0x6610,local_58,0x1000000,&local_78);
    if (BVar4 == 0) {
      puts("CryptDeriveKey() Error\n");
      goto LAB_140001637;
    }
    local_68[0] = '\x01';
    local_68[1] = '\0';
    local_68[2] = '\0';
    local_68[3] = '\0';
    BVar4 = CryptSetKeyParam(local_78,3,local_68,0);
    if (BVar4 == 0) {
      puts("CryptSeKeyParam() Error\n");
      goto LAB_140001637;
    }
    BVar4 = CryptSetKeyParam(local_78,1,(BYTE *)L"IVCanObfuscation",0);
    if (BVar4 == 0) {
      puts("CryptSeKeyParam() with IV Error\n");
      goto LAB_140001637;
    }
    local_68[4] = '\x01';
    local_68[5] = '\0';
    local_68[6] = '\0';
    local_68[7] = '\0';
    BVar4 = CryptSetKeyParam(local_78,4,local_68 + 4,0);
    if (BVar4 == 0) {
      puts("CryptSetKeyParam() with set MODE Error\n");
      goto LAB_140001637;
    }
    nNumberOfBytesToRead = GetFileSize(hFile,(LPDWORD)0x0);
    uVar1 = nNumberOfBytesToRead + 0x10;
    _Size = (ulonglong)uVar1;
    pBVar8 = pbData;
    if (uVar1 != 0) {
      if (_Size < 0x1000) {
        pbData = (BYTE *)operator_new(_Size);
      }
      else {
        if ((ulonglong)uVar1 + 0x27 <= _Size) {
          FUN_140001100();
          pcVar2 = (code *)swi(3);
          (*pcVar2)();
          return;
        }
        pvVar5 = operator_new((ulonglong)uVar1 + 0x27);
        if (pvVar5 == (void *)0x0) goto LAB_140001653;
        pbData = (BYTE *)((longlong)pvVar5 + 0x27U & 0xffffffffffffffe0);
        *(void **)(pbData + -8) = pvVar5;
      }
      pBVar8 = pbData + _Size;
      memset(pbData,0,_Size);
    }
    local_60 = 0;
    BVar4 = ReadFile(hFile,pbData,nNumberOfBytesToRead,&local_60,(LPOVERLAPPED)0x0);
    if (BVar4 == 0) {
      puts("ReadFile() Error\n");
    }
    else {
      lVar6 = -1;
      do {
        lVar6 = lVar6 + 1;
      } while (pbData[lVar6] != '\0');
      local_5c = (int)lVar6 + 1;
      BVar4 = CryptEncrypt(local_78,0,1,0,pbData,&local_5c,0x40);
      if (BVar4 == 0) {
        puts("CryptEncrypt() Error\n");
      }
      else {
        BVar4 = WriteFile(hFile_00,pbData,0x40,(LPDWORD)0x0,(LPOVERLAPPED)0x0);
        if (BVar4 == 0) {
          puts("WriteFile() error\n");
        }
        else {
          CloseHandle(hFile);
          CloseHandle(hFile_00);
          BVar4 = DeleteFileA("flag.txt");
          if (BVar4 == 0) {
            puts("DeleteFileA() error\n");
          }
          else {
            BVar4 = CryptDestroyKey(local_78);
            if (BVar4 == 0) {
              puts("CryptDestroyKey() error\n");
            }
            else {
              BVar4 = CryptDestroyHash(local_58);
              if (BVar4 == 0) {
                puts("CryptDestroyHash() error\n");
              }
              else {
                BVar4 = CryptReleaseContext(local_70,0);
                if (BVar4 == 0) {
                  puts("CryptReleaseContext() error\n");
                }
              }
            }
          }
        }
      }
    }
    if (pbData != (BYTE *)0x0) {
      _Memory = pbData;
      if ((0xfff < (ulonglong)((longlong)pBVar8 - (longlong)pbData)) &&
         (_Memory = *(BYTE **)(pbData + -8), (BYTE *)0x1f < pbData + (-8 - (longlong)_Memory))) {
LAB_140001653:
                    /* WARNING: Subroutine does not return */
        _invoke_watson((wchar_t *)0x0,(wchar_t *)0x0,(wchar_t *)0x0,0,0);
      }
      free(_Memory);
    }
  }
LAB_140001637:
  FUN_140001680(local_38 ^ (ulonglong)auStackY_b8);
  return;
}

かなり複雑に書いてありますが、以下の情報を得ることが出来ます。

  • ReadFile()でflag.txtの内容を読み込む
  • Microsoft Enhanced RSA and AES Cryptographic Providerを使用
  • AES-CBCによる暗号化
  • PKCS5によるPadding
  • SHA-256によるハッシュ生成
  • 共通鍵のPW:ThisIsTheEncryptKey
  • 初期化ベクトルIV:IVCanObfuscation

solution

#include <iostream>
#include <string>
#include <vector>

#include <Windows.h>
#include <Wincrypt.h>
#include <fileapi.h>

int main() {
	HANDLE hFile = CreateFileA("flag.encrypted", GENERIC_READ, FILE_SHARE_READ, 0, 3, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == (HANDLE)-1) {
		printf("flag.encrypted HANDLE Error\n");
		return -1;
	}


	HCRYPTPROV prov;
	if (!CryptAcquireContextW(&prov, 0, L"Microsoft Enhanced RSA and AES Cryptographic Provider", 0x18, 0)) {
		printf("CryptAcquireContextW() Error\n");
		return -1;
	}

	HCRYPTHASH hash;
	if (!CryptCreateHash(prov, 0x800c, 0, 0, &hash)) {
		printf("CryptCreateHash() Error\n");
		return -1;
	}

	const BYTE PW[] = "ThisIsTheEncryptKey";
	if (!CryptHashData(hash, PW, (DWORD)strlen((char*)PW), 0)) {
		printf("CryptHashData() Error\n");
		return -1;
	}

	HCRYPTKEY key;
	DWORD AESlen = 0x1000000;
	if (!CryptDeriveKey(prov, 0x6610, hash, AESlen, &key)) {
		printf("CryptDeriveKey() Error\n");
		return -1;
	}

	BYTE pbData[4];
	pbData[0] = '\x01';
	pbData[1] = '\0';
	pbData[2] = '\0';
	pbData[3] = '\0';

	if (!CryptSetKeyParam(key, 3, pbData, 0)) {
		printf("CryptSetKeyParam() Error\n");
		return -1;
	}

	if (!CryptSetKeyParam(key, 1, (BYTE*)L"IVCanObfuscation", 0)) {
		printf("CryptSetKeyParam() with IV Error\n");
		return -1;
	}

	if (!CryptSetKeyParam(key, 4, pbData, 0)) {
		printf("CryptSetKeyParam() with set MODE Error\n");
		return -1;
	}


	DWORD fileSize = GetFileSize(hFile, NULL);
	std::vector<BYTE> buffer(fileSize + 16);
	DWORD bytesRead = 0;
	if (!ReadFile(hFile, buffer.data(), fileSize, &bytesRead, NULL)) {
		printf("ReadFile() Error\n");
		return -1;
	}

	if (!CryptDecrypt(key, 0, TRUE, 0, buffer.data(), &bytesRead)) {
		printf("CryptDecrypt() Error\n");
		return -1;
	}

	CloseHandle(hFile);
	if (CryptDestroyKey(key) && CryptDestroyHash(hash) && CryptReleaseContext(prov, 0)) {

	printf("here is flag:%s\n", buffer.data());
	
	return 0;
	}else{
		printf("something goes wrong!\n");
		return -1;
	}
}

//実行結果
//here is flag:ctf4b{way_2_90!_y0u_suc3553d_2_ana1yz3_Ma1war3!!!}
ctf4b{way_2_90!_y0u_suc3553d_2_ana1yz3_Ma1war3!!!}

最後に

この問題を作成するに当たり、「初心者向けの問題にすること」「初学者が興味を持ってくれるような問題にすること」を意識しました。特にMAFCについては2年前に応募したセキュリティ系のイベントでの課題にて類題が出されたことがきっかけだったりします。(残念ながら不合格となってしまいました...)
僕自身CTF歴が丁度1年と恐らくAuthorの中で一番歴が短いのではないかと感じています。
これからもCTFに参加し技術知識を身に着けつつ、参加者の皆様がより興味を持っていただけるような問題制作に励んでいきますので是非SECCON Beginners CTF 2026もご参加ください!!!

Discussion