株式会社Ninjastars 技術研究部

「もっと楽しく安全なゲームの世界を創る」

Android 他のプロセスのメモリを読み書きする

株式会社Ninjastarsセキュリティエンジニアの一瀬です。
今日はAndroidアプリケーションでの他プロセスに対するメモリアクセス等について書かせていただきます。
Androidネイティブの情報は少ないので、この記事が読者の皆様の知見になればと思います。

前提
AndroidStudioセットアップ済み
ndk-buildができる状態

環境
rootedな実機 or AndroidEmulator

今回やること等
gdb等のツールなどは使わず、実際にコンパイルしたプログラムで他プロセスのメモリにアクセスし書き換えることを行います。

自作ゲーム:チートチャレンジ - 株式会社Ninjastars 技術研究部
自作ゲーム:チートチャレンジ2 - 株式会社Ninjastars 技術研究部

上記記事はUbuntu18.04の場合でしたが、今回はAndroidで行います。
基本的にAndroidで何がしかのネイティブ処理を行いたい場合、情報量の多いLinuxの内容を修正するというのが良いと思います。

・両者ともLinuxベースのため基本的にはほぼ同じ
Android NDKでは使えないシステムコールや関数が存在する。詳細は下記の方の記事を参照お願いします。
DSAS開発者の部屋:Android NDKで使えないシステムコール・ライブラリ関数一覧

実際にLinuxでメモリアクセスするシステムコールである「process_vm_readv」, 「process_vm_writev」はそのままでは使用できません。
Linuxでのprocess_vm_readv、process_vm_writevの使い方は下記の方のブログを参照ください。
Linuxで他プロセスのメモリを読み書きする方法 - 備忘録
ptraceを利用したメモリアクセスはこのブログで紹介したことがあるので、procfs経由のメモリの読み書きを今回はご紹介させていただきます。

セットアップ手順
適当に作成したフォルダにjniフォルダを作り下記のAndroid.mk、Application.mk、hello.c、memory.cを作成してください。
AndroidStudioのTerminalで

cd 作成したフォルダ
ndk-build

でビルドが出来ます。
libsフォルダにx86、armeabi-v7aのバイナリがそれぞれできますので環境に合わせてコマンドプロンプトから

adb push  (x86 or armバイナリのhelloのパス)/data/local/tmp
adb push  (x86 or armバイナリのmemoryのパス)/data/local/tmp
adb shell
cd data/local/tmp
chmod 755 hello
chmod 755 memory
./hello

Number:0xbfb32078 1
....
等と表示されますがこの0xbfb32078等のアドレスを今回書き換えます。

ps |grep hello
または
ps -A|grep hello

でプロセスIDが確認できます。

./memory 上記のpid 変数のアドレス 1000

memoryが実行されhelloの出力の値が変更されれば成功です。

f:id:Ninjastars:20190107221810p:plain
実行結果

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_CFLAGS += -fPIE
LOCAL_LDFLAGS += -fPIE -pie 
LOCAL_MODULE    := hello
LOCAL_SRC_FILES := hello.c
include $(BUILD_EXECUTABLE)

############################################
include $(CLEAR_VARS)
LOCAL_CFLAGS += -fPIE
LOCAL_LDFLAGS += -fPIE -pie 
LOCAL_MODULE    := memory
LOCAL_SRC_FILES := memory.c
include $(BUILD_EXECUTABLE)

Application.mk

APP_ABI := x86 armeabi-v7a

hello.c

#include<stdio.h>
#include<unistd.h>

int main()
{
      int i = 0;
      while(1)
      {
            i++;
            printf("Number:0x%x %d\n",&i,i);
            sleep(1);
      }
      return 0;
}

memory.c

#include <stdio.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    
    pid_t pid=atoi(argv[1]);
    unsigned long address=strtoll(argv[2],NULL,0);
    int data=strtol(argv[3],NULL,0);

    char file[64];
    sprintf(file, "/proc/%ld/mem", (long)pid);
    int fd = open(file, O_RDWR);

    ptrace(PTRACE_ATTACH, pid, 0, 0);
    waitpid(pid, NULL, 0);
    lseek(fd, address, SEEK_SET);
    write(fd, &data, 4);
    ptrace(PTRACE_DETACH, pid, 0, 0);

}

/proc/pid/memの仮想ファイルシステムをopenすることで、プロセスメモリに対するread/writeが可能となります。



応用
最後に、今回の応用例として逆に不正なメモリ改変を検知するシステムを作成してみました。
動画の保護対象はUnity製アプリケーションでIL2CPPビルドのタイプです。
Unityでビルド設定のScripting BackendをMonoに設定するとメインの処理がC#のAssembly-CSharp.dllに、IL2CPPに設定するとC#C++に変換されlibil2cpp.soにコンパイルされます。
今回のシステムはメモリ上に展開されたlibil2cpp.soを1byteでも不正に改竄すると検知し、アプリケーションを強制終了させます。
また同じ手法で前回の記事で紹介したFridaによる関数フック、Assembly-CSharp.dllの改竄の検知も可能です。

f:id:Ninjastars:20190107222451p:plain
改竄チェックルーチン

動画

Android Memory Protection

このように攻撃技術は転化して防御技術にも利用が可能です。
弊社では常に最新の攻撃手法を研究するとともに、防御面についても何重もの対策を研究しております。


注意事項
本レポートに記載されている内容を許可されていないソフトウェアで行うと、場合によっては犯罪行為となる可能性があります。そのため、記事の内容を試す際には許可されたソフトウェアに対してのみ実施するようにしてください。

本レポートについて
お問い合せ
E-mail:ichise@ninjastars-net.com

株式会社Ninjastarsエンジニア
一瀬健二郎