Tahoo!!

自分の勉強していること(コンピュータ関連 / ネットワーク / セキュリティ / サーバ)や趣味について書いていきます

setodaNote CTF Writeup (Rev)

この記事は、setodaNote CTF RevジャンルのWriteupです。

Helloworld (50pts, 264solves)

問題ファイルはexeファイル。パスワードが infected なのは中々物騒。。

念のため仮想環境でcmd.exeを開いて、実行してみる。

> helloworld.exe
Nice try, please set some word when you run me.

実行する時に何かセットして実行しろと言われるので、引数に何かセットしてみる。

>helloworld.exe 1
Good job, but please set 'flag' when you run me.

flagをセットしろと言われるので、flagを引数にセットして実行してみる。

>helloworld.exe flag
flag{free_fair_and_secure_cyberspace}

flagが表示された。

flag{free_fair_and_secure_cyberspace}

ELF(80pts, 187solves)

問題ファイルはバイナリファイル。

問題名的にELFのバイナリぽいが、とりあえずfileコマンドしてみる。

$ file elf
elf: data

何のファイルとしても認識されない。

バイナリの先頭のデータを16進数表示で見てみる。

$ xxd elf | head
00000000: 5858 5858 0201 0100 0000 0000 0000 0000  XXXX............
00000010: 0300 3e00 0100 0000 5010 0000 0000 0000  ..>.....P.......
00000020: 4000 0000 0000 0000 5031 0000 0000 0000  @.......P1......
00000030: 0000 0000 4000 3800 0b00 4000 1c00 1b00  ....@.8...@.....
00000040: 0600 0000 0400 0000 4000 0000 0000 0000  ........@.......
00000050: 4000 0000 0000 0000 4000 0000 0000 0000  @.......@.......
00000060: 6802 0000 0000 0000 6802 0000 0000 0000  h.......h.......
00000070: 0800 0000 0000 0000 0300 0000 0400 0000  ................
00000080: a802 0000 0000 0000 a802 0000 0000 0000  ................
00000090: a802 0000 0000 0000 1c00 0000 0000 0000  ................

先頭が XXXX となっており、ファイルのmagic numberが潰されているぽい。

問題名的にELFファイルだと考えて、ELFのnagic numberをバイナリエディタで書き換えてみる。ELFのmagic numberは"ELF magic number" などでググると出てくるが、 7F 45 4C 46 である。

f:id:takahoyo:20210907220556p:plain

書き換え後fileコマンドで確認するとELFファイルとして認識されるので、実行権限つけて実行するとFLAG

$ file elf
elf: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4f0f6e7df2d02645bb6387a08a099ddecb22b6f1, for GNU/Linux 3.2.0, stripped
$ chmod +x elf
$ ./elf
flag{run_makiba}

FLAG

flag{run_makiba}

Passcode (120pts, 218solves)

問題ファイルはバイナリファイル。拡張子がないので、fileコマンドで何のファイルか確認する。

$ file passcode
passcode: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=8be572b7a0563868ee29af143b2df0c7d6b1636d, for GNU/Linux 3.2.0, stripped

ELFファイルなので、Linuxで実行権限をつけて実行してみる。

$ chmod +x ./passcode
$ ./passcode
Enter the passcode: aaaaaa
Invalid passcode. Too short.

passcodeを聞かれるがパスワードがわからない。Ghidraで開いて処理を見てみる。

今回のバイナリはstrippedでシンボル情報が剥がされてるのでmainの処理がぱっと見どこかわからない。

Defined Stringsから "Enter the passcode" や "Invalid passcode." などの文字列を探し、それらを参照している関数を調べる。FUN_00101185 から参照されているらしいので、その関数のデコンパイル結果を見てみる。

以下の部分でパスワードの確認をしており、strcmp で "20150109" と入力した文字列を比較しているので、"20150109" がpasscodeぽい。

iVar1 = strcmp((char *)&local_108,"20150109");
if (iVar1 == 0) {
  puts("The passcode has been verified.\n");
  printf("Flag is : flag{%s}",&local_108);
}
else {
  printf("Invalid passcode. Nice try.");
}

正解のパスコードがFLAG

flag{20150109}

Passcode2 (150pts, 109solves)

問題ファイルはバイナリファイル。passcodeと同様にfileコマンドでファイルの種類を調べる。

$ file passcode2
passcode2: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a396332a87a60f8e353e93a001a1a9521673f19d, for GNU/Linux 3.2.0, stripped

ELFなのでLinuxで実行してみる。

$ chmod +x ./passcode
$ ./passcode2 
Enter the passcode: aaaaaaaa
Invalid passcode. Too short.

passcodeと同じエラーが出る。

passcodeと同様にGhidraで開いて、Defined Stringsからpasscodeをチェックしている関数を特定。FUN_00101175 でチェックしてる。

今回、passcodeを比較しているのは以下の部分。

sVar3 = strlen((char *)&local_118);
if (sVar3 == 0xb) {
  local_10 = 0;   while ((sVar3 = strlen((char *)local_124), local_10 < sVar3 &&
           (*(byte *)((long)&local_118 + local_10) == (local_124[local_10] ^ 0x2a)))) {
      local_10 = local_10 + 1;
  }
  sVar3 = strlen((char *)local_124);
  if (local_10 == sVar3) {
    puts("The passcode has been verified.\n");
    printf("Flag is : flag{%s}",&local_118);
  }
  else {
    printf("Invalid passcode. Nice try.");
  }
}
else {
  printf("Invalid passcode.");
}

local_124 の配列に格納されている文字列をXORで0x2aしたものと比較している。

Ghidraがデコンパイルした local_124の配列は以下のとおり。

local_124[0] = 0x18;
local_124[1] = 0x1f;
local_124[2] = 4;
local_124[3] = 0x79;

しかし、直前で入力したpasscodeの文字列の長さが0xb(11)文字かを確認している(if(sVar3 == 0xb)のところ)ので、これは短すぎる。

これはGhidraが間違ってデコンパイルしており、実際には以下のデータを0x2aでXORしている。(デコンパイラに頼りすぎるのもダメですね。。

local_124[0] = 0x18;
local_124[1] = 0x1f;
local_124[2] = 4;
local_124[3] = 0x79;
local_120 = 0x4f;
local_11f = 0x5a;
local_11e = 4;
local_11d = 0x18;
local_11c = 0x1a;
local_11b = 0x1b;
local_11a = 0x1e;

あとはCyberChefなりなんなりで、これらの値を0x2aでXORすると 25.Sep.2014 となる。

https://gchq.github.io/CyberChef/#recipe=From_Hex('Auto')XOR(%7B'option':'Hex','string':'2a'%7D,'Standard',false)&input=MHgxOCAweDFmIDB4MDQgMHg3OSAweDRmIDB4NWEgMHgwNCAweDE4IDB4MWEgMHgxYiAweDFl

これをpasscodeとして入力すると、正しい答えであることを確認できた。

$ ./passcode2
Enter the passcode: 25.Sep.2014 
The passcode has been verified.

Flag is : flag{25.Sep.2014}

FLAG

flag{25.Sep.2014}

to_analyze (200pts, 82solves)

問題ファイルはexeファイル。またパスワードが infected なので、念のため仮想環境のWIndowsで実行してみる。

> to_analyze.exe
nice try!

何もわからんので、ファイルを解析する。

まず、PeStudio でどのような特性のファイルか見てみる。signatureを見ると、どうやら Microsoft Visual C# v7.0 / Basic .NET で作られたファイルらしい。

f:id:takahoyo:20210907220420p:plain

.NETで作られた実行ファイルであればデコンパイルできる。 .NET decompilerであるILSpy を使ってコードをデコンパイルする。

f:id:takahoyo:20210907220408p:plain

ILSpyで開くとデコンパイルされたコードが出てくる。DotfuscatorAttribute といういかにも難読化してそうなクラス名があるので、ググるとDotfuscatorという難読化ツールがあるらしい。

de4dot という難読化解除ツールがあるらしいので試してみる。

C:\Users\tanaka\Desktop>de4dot -f to_analyze.exe -o to_analyze_de4dot.exe

de4dot v3.1.41592.3405 Copyright (C) 2011-2015 de4dot@gmail.com
Latest version and source code: https://github.com/0xd4d/de4dot

Detected Dotfuscator (C:\Users\tanaka\Desktop\to_analyze.exe)
Cleaning C:\Users\tanaka\Desktop\to_analyze.exe
Renaming all obfuscated symbols
Saving C:\Users\tanaka\Desktop\to_analyze_de4dot.exe

すべての難読化されたシンボルをrenameしてくれた模様。出力されたファイルを改めてILSpyで開いてみると、メソッド名やフィールド名が一部renameされていて読みやすくなっている。

f:id:takahoyo:20210907220354p:plain

実行したときに "Nice Try" と表示した場所を見てみる。smethod_2 の引数で渡されたディレクトリ名の存在有無で処理を分けており、特定のディレクトリが存在した場合に答えが表示されるようだ。

private static void smethod_2(string string_0, byte[] byte_0)
{
    if (Directory.Exists(string_0))
    {
        Console.WriteLine("Yes, that's the right answer.");
        ...<snip>...
    }
    else
    {
        Console.WriteLine("nice try!");
    }
}

ifの処理を追えばFLAGがわかりそうではあったが、今回はマルウェア解析ぽくifで正解に分岐する条件を調べてみる。

smethod_2Main から呼ばれおり、Main では array に格納されたデータを文字列として渡している。

using System.Text;

private static void Main(string[] args)
{
    byte[] array = new byte[15]
    {
        65, 127, 89, 80, 182, 160, 183, 182, 89, 118,
        119, 116, 177, 189, 177
    };
    for (int i = 0; i < array.Length; i++)
    {
        array[i] ^= 35;
        if (smethod_1(array[i], 119))
        {
            array[i] += 3;
        }
        array[i] ^= 21;
        array[i] -= 32;
        array[i] = smethod_0(array[i], 39);
    }
    smethod_2(Encoding.ASCII.GetString(array), array);
}

array65, 127, 89, 80, 182, 160, 183, 182, 89, 118, 119, 116, 177, 189, 177 で初期化されているが、後の処理でこれらのデータに対して様々な計算を行っているので、エンコードされていると考えられる。

途中呼ばれている smethod_1 および smethod_0 の処理は以下のとおり。

private static bool smethod_1(byte byte_0, int int_0)
{
    if (int_0 == 119)
    {
        if (byte_0 == 107 || byte_0 == 117 || byte_0 == 108 || byte_0 == 102 || byte_0 == 98)
        {
            return true;
        }
    }
    else if (byte_0 == 110 || byte_0 == 119 || byte_0 == 99 || byte_0 == 111 || byte_0 == 97 || byte_0 == 101 || byte_0 == 112 || byte_0 == 103 || byte_0 == 108 || byte_0 == 107 || byte_0 == 112 || byte_0 == 113)
    {
        return true;
    }
    return false;
}

private static byte smethod_0(byte byte_0, int int_0)
{
    switch (int_0)
    {
    case 114:
        byte_0 = (byte)(byte_0 ^ 0x28u);
        break;
    case 39:
        byte_0 = (byte)(byte_0 ^ 0x13u);
        break;
    }
    return byte_0;
}

これらを踏まえて、smethod_2 に渡されるディレクトリ名をデコードするスクリプトPythonで書いた。

#! /usr/bin/env python

nums = [65, 127, 89, 80, 182, 160, 183, 182, 89, 118,119, 116, 177, 189, 177]
  
directory_name = ''
for n in nums:
  n ^= 35
  if ( n == 107 or n == 117 or n == 108 or n == 102 or n == 98):
    n += 3
  n ^= 21
  n -= 32
  n ^= 0x13
  directory_name += chr(n)

print(directory_name)
py -3 decode.py
C:\Users\321txt

渡されるディレクトリ名が C:\Users\321txt であることがわかるので、このディレクトリを作って、バイナリを実行してみる。

f:id:takahoyo:20210907220331p:plain

FLAG

flag{Do_y0u_Kn0w_Ursnif?}

マルウェア Ursnifが使ってるテクニックの一つなのかな?

ちなみに今回は解析にFLARE VMを使ったが、今回必要な解析ツールが一通り揃っていたので楽だった。なお、ユーザ名とかもバッチリ写ってるので念の為言っておくと、仕事で使ってる環境じゃなくて個人で使ってる環境ですw