本ブログは米国で2019年07月01日に公開されたUnit 42ブログ「The Gopher in the Room: Analysis of GoLang Malware in the Wild」の日本語翻訳です。

 

 

エグゼクティブサマリー

筆者はここ数カ月 Go プログラミング言語で書かれたマルウェアに強い関心を持っていました。Go は GoLang という名で呼ばれることもあるプログラミング言語で、2009 年に Google によって作成されたものです。近年、この言語はマルウェア開発コミュニティでも人気を集めています。

Go マルウェアファミリについて扱ったブログも増えています。そこで、マルウェア開発という意味で当該プログラミング言語が実際にどの程度普及しているのかを知りたくなりました。これにくわえて、Go は主にペネトレーションテスターやレッドチーム演習者が使用する言語であるという認識が広がっているなか、実際にはどのマルウェアファミリが最も流行しているのかを知りたくなりました。それらを念頭に置き、筆者はできるだけ多くの Go で書かれたマルウェアを収集し、それをマルウェアファミリごとに分類することに着手しました。本稿では、そのさいのデータ収集方法とそこから得られた結果について説明します。

合計で、Go で書かれたユニークなサンプルは、およそ 10,700 例得られました。これらサンプルが最初に確認された日 (初認日) のタイムスタンプから見て、Go でコンパイルされたマルウェアは数ヶ月のスパンで着実に増加していると結論付けることができます。さらに、識別されたサンプルの 92% が Windows OS 用にコンパイルされたものであり、これは Go マルウェア開発者に最も集中的に狙われているシステムであることを示しています。

得られたサンプルの 75% については、出自となるマルウェアファミリを特定することができました。最も目立ったマルウェアファミリとしては、VeilGoBot2、そして HERCULES が挙げられます。また、最も普及していたマルウェア グループの内訳は、ペネトレーションテストツール、バックドア、リモートアクセス型トロイの木馬 (RAT) などでした。

なおここでは、マルウェアファミリのもつ機能の違いに基づいて、バックドアと RAT とを区別しています。つまり、リモートアクセスのためのシンプルな最低限の機能を提供しているものはバックドアとして分類し、リモートアクセスのために多種多様な機能を備えたトロイの木馬は RAT として分類しています。

なぜ Go で書くのか


Go には、攻撃者がこの特定プログラミング言語を使用したくなるような機能が複数備わっています。そうした Go 最大の魅力の 1 つが、Windows、OS X、Linux など、すべての主要 OS プラットフォーム用に、単一コードベースをコンパイルできることです。これにより、攻撃者は単一のコードベースに集中するだけで、さまざまなプラットフォームを感染対象とすることができます。これがほかのプログラミング言語であれば、それぞれのプラットフォームごとに異なるコードリポジトリを持たねばならない場合が多いでしょう。

ほかに単一コードベースに集中する方法としては Python など汎用スクリプト言語を使用してコードベースを作成する方法も挙げられます。これは以前にペイロードの 1 つを Python で書いていた Chafer 脅威攻撃グループでも見られました。このほか、Seaduke マルウェアファミリも、こうしたアプローチを採用していた脅威攻撃グループのひとつです。ただし、Windows は歴史的には環境内でネイティブに Python を提供してこなかったため、これらのコードベースが Windows 環境で正しく実行されるようにするには PyInstaller のようなユーティリティを使ってコードベースをパッケージ化する方法に頼らざるをえません。たしかにそうしたツールを使えば目的を達することはできるものの、実行時にドロップされるファイル内には多数の痕跡が残ってしまいます。一方、Go ならこうした痕跡 (アーティファクト) を一切残さないので、これが攻撃者にとっての利点となる可能性があります。

Go のもう 1 つの利点 (ただし見かたによっては短所) は、必要なライブラリがすべてコンパイル済みバイナリ内で静的にリンクされていることです。なお一般に、静的にリンクされたバイナリのサイズは、平均的なマルウェアよりもサイズが大きくなる傾向があります。Go で書かれた 10,700 例のマルウェアサンプルについても、平均サイズは 4.65 MB でした。この平均サイズはマルウェアの平均サイズを大きく上回っているためトロイの木馬パッケージで使用することが難しくなっていました。くわえて、添付ファイルのサイズが大きくなれば電子メールサーバーに許可されないおそれもあることから、フィッシングメールに含めにくくなる可能性があります。

ただし、サイズが大きくなることには予期せぬ利点もあります。特定の状況では、ウイルス対策製品が、ファイルが大きすぎるとファイルを無視したり、ファイルをスキャンできなくなることがあります。これを狙った標的型攻撃が目撃されたこともあります。たとえば Comnie マルウェアファミリのマルウェア作者は、アンチウイルス製品を迂回するために 64MB 分のゴミデータを自身のファイルに追加していたことがありました。

手法
 

本調査を開始するにあたり、Go でコンパイルされた可能な限り多くのマルウェアサンプルを収集することからはじめたのですが、このタスクひとつとっても本調査はかなり難航することがわかりました。調査用リポジトリとしては、弊社のリポジトリに加え、サードパーティの VirusTotal サービス のリポジトリも利用し、識別可能なすべての Go サンプルを悪意のあるなしにかかわらず単純に収集することから始めました。

これらサンプル収集にあたっては、以下を含む多くのアプローチを取りました。

  • 「Go.org」を参照する埋め込みURLを持つ OS X または Linux のサンプル
  • 「Go-http-client/1.1」という User-Agent を使っているサンプル
  • 「GRequests」という User-Agent を使っているサンプル
  • 「.symtab」というセクション名を含む PE サンプル
  • 識別済みの一連のインポートハッシュを使用している PE サンプル
  • Google の gopacket github リポジトリを参照している OS X サンプル
  • gopkg.in を参照する OS X サンプル
  • YARA ルールに一致するサンプル

YARA ルールについては、異なるルール 3 つを作成し、主要プラットフォームそれぞれのための Go サンプルを識別しました。たとえば、OS X 用にコンパイルされた Go サンプルを識別するには、次のルールを使いました。


    rule osx_GoLang
    {
    meta:
         author = "Josh Grunzweig"
         description = "Attempts to identify samples written in Go
         compiled for OSX."
         strings:
              $Go = "go.buildid"
         condition:
              (
                   uint32(0) == 0xfeedface or
                   uint32(0) == 0xcefaedfe or
                   uint32(0) == 0xfeedfacf or
                   uint32(0) == 0xcffaedfe or
                   uint32(0) == 0xcafebabe or
                   uint32(0) == 0xbebafeca
              ) and
              $Go
    }

これらさまざまなテクニックを使用し、およそ 611,000 例のユニークなサンプルを集めることができました。

これらすべてのサンプルについてハッシュを取得後、弊社システムと VirusTotal の両方に問い合わせを行い、どのサンプルに悪意があるかを判断しました。至っててVirusTotal については単純に、「マルウェア」または 5 つ以上プラスの判定があるものをチェックしました。判定後に残ったサンプルをダウンロードし、先に作成した YARA ルールを実行して、それらが実際に Go のサンプルであることを確認しました。すべての処理が終わると、手元にはおよそ 13,000 例のユニークなサンプルが残りました。

こうしてデータセットを作り出した後は、それらサンプルをそれぞれのマルウェアファミリに分類する作業に着手しました。この作業は主に手動で行われ、特定ファイルを分析しては、識別されたマルウェアファミリに基づいた YARA ルールを作成しました。また、作業を効率化するため、識別済みバイナリから有用な情報を抽出するヘルパースクリプトも書きました。次のヘルパースクリプトは、バイナリからのユーザー定義関数名抽出と (ある場合は) ユーザー定義パス抽出を行うものです。


    import sys
    import re
    inputfile = sys.argv[1]
    fh = open(inputfile, ‘rb’)
    fd = fh.read()
    fh.close()
    minimum = 5
    char = r”[\t\n\x20-\x7f]” + “{{{},}}”.format(minimum)
    wchar = r”(([\t\n\x20-\x7f]\x00)” + “{{{},}}”.format(minimum) + r”\x00)”
    allStrings = []
    for s in re.findall(char, fd):
         allStrings.append(s)
    for s in re.findall(wchar, fd):
         allStrings.append(s[0].replace(“\x00″,”))

    blacklist = []
    allStr = []
    for s in allStrings:
         if s[-3:] == “.go” and “main.go” in s:
              allStr.append(s)
         elif s[0:5] == “main.”:
              if “statictmp” not in s:
                   if “.init.” not in s:
                        if “.(*” not in s:
                             allStr.append(s)
    for x in list(set(allStr)):
         print(repr(x))

このヘルパースクリプトの実行例を次に示します。


    $ python find_interesting_strings.py fc684bbf9428a4e33c390e3963c9bfa24e81cb040ccd601c6e7f5b6c193e2808.bin
    ‘main.encryptFile’
    ‘main.writeLog.func1’
    ‘main.writeLog’
    ‘main.init’
    ‘main.scanDir’
    ‘main.ignoreUsersFolders’
    ‘main.ignoreRootFolders’
    ‘main.encryptFile.func1’
    ‘main.logFilePath’
    ‘main.ignoreProgramFilesFolders’
    ‘C:/Users/pc/go/src/scaner/main.go’
    ‘main.ignoreProgramDataFolders’
    ‘main.initdone’
    ‘main.makeReadmeFile.func1’
    ‘main.ignoreFiles’
    ‘main.ignoreFileExtensions’
    ‘main.main’
    ‘main.makeReadmeFile’
    ‘main.DEBUG’

これでどんな関数名やコードパスが最も一般的かを簡単に判断できるようになり、場合によってはこの情報だけでサンプルを分類することができました。あるマルウェアファミリ用の YARA ルールのサンプルを次に示します。


    rule trojan_golang_hercules: Pentesting
    {
         meta:
              author = “jgrunzweig – PaloAltoNetworks”
              date = “2019-06-15”
              description = “the HERCULES malware family written in
              Go.”
              hash1 = “2a7da0a0acadb61fb79fa4a33130d09ecff5a904b0999d264d8c1edffeffea95”
              hash2 = “6e68dafbb717daf6a505d8a95c41e5114d91c4fde703343356352c1ca5cd24ea”
              hash3 = “645ed38f2d55b2f7731d5c9223329428592497eb95c96bcd7c01a4eaeb38e137”
              reference = “https://github.com/EgeBalci/HERCULES”
         strings:
              $buildid = “go.buildid”
              $uniq1 = “cGFja2FnZSBtYWluCgppbXBvcnQgIm5ldCIKaW1wb3J0ICJvcy9leGVjIgppbXBvcnQgImJ1ZmlvIgppbXBvcnQgInN0cmluZ3MiCmltcG9ydCAic3lzY2FsbCIKaW1wb3J0ICJ0aW1lIgppbXBvcnQgIkVHRVNQTE9JVCIKCgoKY29uc3QgSVAgc3RyaW5nID0gIjEwLjEwLjEwLjg0Igpjb25zdCBQT1JUIHN0cmluZyA9ICI1NTU1IgoKY29uc3QgQkFDS0RPT1IgYm9vbCA9IGZhbHNlOw”
              $uniq2 = “cGFja2FnZSBtYWluCgoKaW1wb3J0ICJlbmNvZGluZy9iaW5hcnkiCmltcG9ydCAic3lzY2FsbCIKaW1wb3J0ICJ1bnNhZmUiCi8vaW1wb3J0ICJFR0VTUExPSVQvUlNFIgoKY29uc3QgTUVNX0NPTU1JVCAgPSAweDEwMDAKY29uc3QgTUVNX1JFU0VSVkUgPSAweDIwMDAKY29uc3QgUEFHRV9BbGxvY2F0ZVVURV9SRUFEV1JJVEUgID0gMHg0MAoKCnZhciBLMzIgPSBzeXNjYWxsLk5ld0”
              $uniq3 = “cGFja2FnZSBtYWluCgppbXBvcnQgIm5ldC9odHRwIgppbXBvcnQgInN5c2NhbGwiCmltcG9ydCAidW5zYWZlIgppbXBvcnQgImlvL2lvdXRpbCIKLy9pbXBvcnQgIkVHRVNQTE9JVC9SU0UiCgoKCmNvbnN0IE1FTV9DT01NSVQgID0gMHgxMDAwCmNvbnN0IE1FTV9SRVNFUlZFID0gMHgyMDAwCmNvbnN0IFBBR0VfQWxsb2NhdGVVVEVfUkVBRFdSSVRFICA9IDB4NDAKCnZhciBLMzIgPS”
             $path = “/HERCULES/”

             $banner = “HERCULES REVERSE SHELL”
             $help1 = “~DOS -A \”www.targetsite.com\””
             $help2 = “~WIFI-LIST “
             $help3 = “~KEYLOGGER-DUMP “
             $help4 = “Creates a reverse http meterpreter session at given pid (EXPERIMENTAL)”

         condition:
              (
                    // Windows binary
                   (uint16(0) == 0x5a4d) or
                   // OSX binary
                   (
                        (
                             uint32(0) == 0xfeedface or
                             uint32(0) == 0xcefaedfe or
                             uint32(0) == 0xfeedfacf or
                             uint32(0) == 0xcffaedfe or
                             uint32(0) == 0xcafebabe or
                             uint32(0) == 0xbebafeca)
                        ) or
                        // Linux binary
                        (uint32(0) == 0x464C457F)
                   ) and
                   filesize > 500KB and
                   $buildid and
                   (
                        any of ($uniq*) or
                        $banner or
                        any of ($help*) or
                        $path
                   )
    }

この作業を手動で行ったことで、誤検知も特定できました。作業が終わったとき、最終的には約 2,000 例の誤検知を確認できました。この結果、残ったマルウェア サンプル総数は 10,700 例になり、そのうち 75% がマルウェアと識別されました。

本調査では合計 53 例のユニークなマルウェアファミリが特定され、それぞれに対して YARA ルールが作成されました。
 

結果

本調査から導き出される最も明確な結論の 1 つは、識別済みのマルウェア ファイルのうち Go でコンパイルされたファイルは比較的少数であるということでしょう。この結果が筆者の手法に依存している可能性は確かにあるものの、これは全体としてはかなり正確な数値であると考えています。マルウェア開発言語としての Go はまだ揺籃期にあり、マルウェア開発コミュニティで本当に高い人気を得るには至っていません。とはいえ、Go によるマルウェアサンプルの初認日別タイムラインを眺めれば、人気が高まりつつある様子は見てとれます。

 

図 1 初認日に基づくGo マルウェアサンプルのタイムライン

本調査から導き出されるもう 1 つの興味深い結論は、Go マルウェア サンプルが最も頻繁にコンパイルされた対象となる OS を特定することでした。合計では大多数が Windows 用に書かれていましたが、これは多くの人にとって驚くことではないかもしれません。

 

図 2 Go マルウェア サンプルのコンパイル対象 OS

合計では、識別された Go マルウェア サンプルのうち 92% が Windows OS 用にコンパイルされ、4.5% が Linux 用、残り 3.5% が OS X 用にコンパイルされていました。Windows OS は攻撃者が最も集中的に狙うプラットフォームであり続けているためこのデータは驚くべきことではありませんが、筆者個人としては、本調査に入ることで Windows OS が識別済みマルウェア全体にしめる割合がそれほど高くないことがわかるのではないかと考えていました。

なお本調査の過程では先に述べたように合計 53 例のマルウェアファミリが特定されました。その結果は以下のとおりです。

マルウェア ファミリ マルウェアの数 (とその割合)
Veil 3772 (47%)
GoBot2 1025 (12.8%)
HERCULES 475 (5.9%)
CHAOS 471 (5.9%)
Generic Coinminer 406 (5.1%)
Infostealer 360 (4.5%)
TinyBanker 182 (2.3%)
GoBrut 165 (2.1%)
Neshta 164 (2.1%)
ARCANUS 150 (1.9%)
Gandalf Botnet 120 (1.5%)
hershell 86 (1.1%)
rocke 60 (0.75%)
Infostealer Variant B 44 (0.55%)
GoBot 43 (0.53%)
Shellcode Loader Variant B 41 (0.51%)
Downloader Variant D 41 (0.51%)
ShurL0ckr 37 (0.46%)
Mirai 36 (0.45%)
merlin 35 (0.44%)
EGESPLOIT 32 (0.4%)
Downloader Variant B 32 (0.4%)
Mauri870 Ransomware Family 27 (0.34%)
nett Botnet 21 (0.26%)
gscript 18 (0.23%)
Malicious FireFox Extension Loader 14 (0.18%)
Supic Backdoor 12 (0.15%)
r2r2 11 (0.14%)
RobbinHood 10 (0.13%)
jimm Ransomware 9 (0.11%)
braincrypt 9 (0.11%)
Rakos 8 (0.1%)
TrumpHead Ransomware 7 (0.08%)
HTRAN 7 (0.08%)
Keylogger Variant A 7 (0.08%)
YourRansom Ransomware 5 (0.06%)
RDW 5 (0.06%)
Ransomware Variant A 5 (0.06%)
Italian Downloader 4 (0.05%)
Scanner Variant A 4 (0.05%)
Shifr Ransomware 4 (0.05%)
Shellcode Loader Variant A 4 (0.05%)
go-bot 3 (0.04%)
Exploit Utility Variant A 3 (0.04%)
Zebrocy 3 (0.04%)
Downloader Variant C 3 (0.04%)
Downloader Variant A 3 (0.04%)
RaaS Ransomware 3 (0.04%)
goshell 2 (0.03%)
TeleGrab 2 (0.03%)
gorsh 2 (0.03%)
Czech Downloader 1 (0.01%)
Bitfinex Lending Bot 1 (0.01%)
合計: 7997 (100%)

 

表1 特定された Go マルウェアファミリ

調査結果をさまざまな方法で表現するため、個々のマルウェアファミリをその属性と目的に基づいてさまざまなカテゴリに分類しました。結果は以下のとおりです。

 

図 1 Go マルウェアのカテゴリ

ご覧のとおり、識別されたファイルの大半はペネトレーションテスト活動関連です。これらのファイルが悪意を持って使用される可能性もありますが、意図される用途はペネトレーションテストです。大多数のサンプルがこうしたテスト目的という特徴を持っていましたが、合法的な目的のないマルウェア サンプルもいくつか確認されています。RAT、バックドア、コインマイナー、および情報窃取ツールが残りのカテゴリのリスト上位を占めています。
 

結論

全体として、本研究調査は多くの理由から筆者個人にとって啓発的なものとなりました。ペネトレーションテスト関連の Go マルウェアの蔓延、という先入観が実際に確認できたのと同時に、それとは別にさまざまな真のマルウェア ファミリが存在することも確認できました。これらのマルウェア ファミリは、バックドアからボットネット、バンキング型トロイの木馬までさまざまです。マルウェア サンプルの全体数が少ないことも興味深いデータポイントで、一般論として Go マルウェアはまだマルウェア開発者からは大きな関心を集めていないことが示されています。ただし、識別済みマルウェアサンプルの初認日別タイムスタンプのタイムラインからは、Go マルウェアの人気が上がりつつあることもわかります。2017 年から 2019 年までの 1 月から 3 月という特定期間で見ると、識別されたマルウェアサンプル数は 20 倍近く (1944 %) 増加しています。

Go によるマルウェアはまだその揺籃期にありますが、新しいマルウェア ファミリが相次いで発見されてその情報が公開されていることから、マルウェア開発者、セキュリティコミュニティ一般の両方から注目が集まっています。すべての主要 OS に単一コードベースをコンパイル可能であることから、Go は今後数年で開発されるマルウェアでより大きな市場シェアを占めることになると筆者は考えますし、セキュリティコミュニティ側も Go によるマルウェアをレーダーに捕捉しておくべきしょう。

本稿で言及したすべての調査結果はパロアルトネットワークス製品による保護に使用されています。
 

IOC

セキュリティコミュニティを支援するため、ハッシュ値と対応する YARA ルールマッチの完全なリストを公開しています。ダウンロードはこちらから行ってください。