株式会社Ninjastarsセキュリティエンジニアの一瀬です。
今日はAndroidアプリケーションでのLD_PRELOAD攻撃とその対策について書かせていただきます。
チート行為などにおいてはLD_PRELOADを利用した攻撃は一般的ではありませんが、存在を知らないとリスク対策もできないのでご紹介いたします。
前提
Android Studioセットアップ済み
ndk-buildができる状態
環境
rootedな実機 or Android Emulator
今回やること
LD_PRELOADで動的ライブラリ関数を上書きする | Siguniang's Blog
まず上記の方の実験用プログラムをAndroidで動かすことを目標とします。
また今回はその対策手法についてもご説明させていただきます。
セットアップ手順
適当に作成したフォルダにjniフォルダを作り下記のAndroid.mk、Application.mk、random_num.c、unrandom.c、myrandom.cを作成してください。
AndroidStudioのTerminalで
cd 作成したフォルダ
ndk-build
でビルドが出来ます。
libsフォルダにx86、armeabi-v7aのバイナリがそれぞれできますので環境に合わせてコマンドプロンプトから
adb push (x86 or armバイナリのrandom_numのパス) /data/local/tmp adb push (x86 or armバイナリのlibunrandomのパス) /data/local/tmp adb push (x86 or armバイナリのlibmyrandomのパス) /data/local/tmp adb shell cd data/local/tmp chmod 755 random_num
AndroidにおけるLD_PRELOADの設定
1.端末上でSELinuxを無効化。
2a.アプリケーションの場合:
setpropでアプリのパッケージに対して LD_PRELOADを設定する。この時パッケージ名にwrap.を付加する。
例えば対象アプリのパッケージ名が"com.doraneko.SurvivalShooter"、読み込ませたいsoファイルのパスが/data/local/tmp/libinject.soの場合
setprop wrap.com.doraneko.SurvivalShooter LD_PRELOAD=/data/local/tmp/libinject.so
2b.実行ファイルの場合:
例えば実行ファイル名がhello、読み込ませたいsoファイルのパスがdata/local/tmp/libinject.soの場合
LD_PRELOAD=./data/local/tmp/libinject.so ./hello
実験
それでは実際にrandom_numに対してlibunrandom.so、libmyrandom.soをLD_PRELOADを用いてインジェクションしてみます。
LD_PRELOAD=./data/local/tmp/libunrandom.so ./random_num LD_PRELOAD=./data/local/tmp/libmyrandom.so ./random_num
見事にrandom関数がインジェクションしたsoファイルの関数に置換されています。また画像のようにsoファイルがインジェクションされていることが分かります。
このように既存の関数を置換される危険性があるとともに、soファイルのインジェクション自体がその他の攻撃の起点にもなりうる可能性があります。
一般的なチート手法ではGameGuardianやgdb、Fridaなどを用いてptraceでの対象プロセスへのアタッチが必要となります。
しかしLD_PRELOAD攻撃の場合ptraceでのアタッチを伴わずsoファイルのインジェクションや関数の置換が可能となります。
常にこうした攻撃手法が存在するということを念頭に置いて、防御面も考えていく必要があるかと思われます。
ソースコード
※AndroidStudioでndkビルドするとソースコード上でrand関数で記述してもlrand48関数でコンパイルされるようなので、unrandom.c、myrandom.cでは置換すべき関数としてlrand48で記述しています。
Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_CFLAGS += -fPIE LOCAL_LDFLAGS += -fPIE -pie LOCAL_MODULE := random_num LOCAL_SRC_FILES := random_num.c include $(BUILD_EXECUTABLE) ############################################ include $(CLEAR_VARS) LOCAL_MODULE_FILENAME:= libunrandom LOCAL_MODULE := unrandom LOCAL_SRC_FILES := unrandom.c include $(BUILD_SHARED_LIBRARY) ############################################ include $(CLEAR_VARS) LOCAL_MODULE_FILENAME:= libmyrandom LOCAL_MODULE := myrandom LOCAL_SRC_FILES := myrandom.c include $(BUILD_SHARED_LIBRARY)
Application.mk
APP_ABI := x86 armeabi-v7a
random_num.c
#include <stdio.h> #include <stdlib.h> #include <time.h> int main(){ srand(time(NULL)); int i = 10; while(i--) printf("%d\n",rand()); return 0; }
unrandom.c
int lrand48(){ return 42; //the most random number in the universe }
myrandom.c
#define _GNU_SOURCE #include <dlfcn.h> #include <stdio.h> typedef int (*orig_rand_f_type)(); int lrand48(){ printf("custom rand is called\n"); orig_rand_f_type orig_rand; orig_rand = (orig_rand_f_type)dlsym(RTLD_NEXT, "lrand48"); return orig_rand(); }
防御手法
通常の方法でアプリケーションを実行した場合、親プロセスはzygoteになります。
USER PID PPID VSIZE RSS WCHAN PC NAME root 1841 1 1611188 125080 0 b7700c60 S zygote ...... u0_a48 4267 1841 1112668 145184 0 b7700c60 S com.doraneko.SurvivalShooter
しかしLD_PRELOADを設定した状態でアプリを起動すると親はzygoteでなくなります。(実際は/system/bin/sh)
USER PID PPID VSIZE RSS WCHAN PC NAME root 1841 1 1611188 125080 0 b7700c60 S zygote ........ u0_a48 4569 4557 1125376 189684 0 b7720c60 S com.doraneko.SurvivalShooter
つまり対策の一つの手法としては親プロセスがzygoteであるか確認する方法が有効です。
ただここで注意しなくてはいけないのは、チェック処理で標準関数を使用するとそれ自体攻撃者によって置換されてしまう可能性があるということです。
実際上は攻撃手法に対する対策だけでなく、その対策の回避方法に対する更なる対策など何重にも防御していく必要があります。
最後に対象アプリケーションの送受信関数をLD_PRELOADで置換して、HTTP通信の内容をlogcatで出力するプログラムを作成しました。
LD_PRELOADによるHTTP通信キャプチャ
注意事項
本レポートに記載されている内容を許可されていないソフトウェアで行うと、場合によっては犯罪行為となる可能性があります。そのため、記事の内容を試す際には許可されたソフトウェアに対してのみ実施するようにしてください。
本レポートについて
お問い合せ
E-mail:ichise@ninjastars-net.com
株式会社Ninjastarsエンジニア
一瀬健二郎