Android エミュレーターの検出に関するオープンソース プロジェクト/記事/論文をいくつか読みました。私が読んだのは、実際には 2013 年と 2014 年に提案された方法です。これらの方法のほとんどは、何らかの環境特性を検出し、いくつかのファイルをチェックしますが、実際には検出のアイデアはこれに限定されません。いくつかの方法は qemu を検出するのに非常に直接的ですが、他の方法は adb の検出、ptrace の検出など間接的です。考え方も非常に柔軟です。 ***QEMU などのシミュレートされた CPU と物理 CPU の実際の違い (タスク スケジューリングの違い)、シミュレートされたセンサーと物理センサーの違い、キャッシュの違いを活用して CPU を検出する方法を使用することを提案する人もいます。環境属性を検出する場合と比較して、検出効果が大幅に向上します。 [[228598]] 以下に、誰もがコミュニケーションをとり、学習できるように、さまざまな資料で提案されているいくつかの方法/アイデア/コードをリストします。 QEMU プロパティ - パブリッククラスプロパティ{
- パブリック文字列名;
- パブリック文字列 seek_value;
-
- パブリックプロパティ(文字列名、文字列シーク値) {
- this.name =名前;
- this.seek_value = シーク値;
- }
- }
- /**
- * 現在の環境がQEMUであるかどうかを判断するために使用される既知の属性([属性名、属性値]の形式)
- */
- プライベート静的プロパティ[] known_props = {新しいプロパティ( "init.svc.qemud" 、 null )、
- 新しいプロパティ( "init.svc.qemu-props" 、 null )、新しいプロパティ( "qemu.hw.mainkeys" 、 null )、
- 新しいプロパティ( "qemu.sf.fake_camera" 、 null )、新しいプロパティ( "qemu.sf.lcd_density" 、 null )、
- 新しいプロパティ( "ro.bootloader" , "unknown" )、新しいプロパティ( "ro.bootmode" , "unknown" )、
- 新しいプロパティ( "ro.hardware" 、 "goldfish" )、新しいプロパティ( "ro.kernel.android.qemud" 、 null )、
- 新しいプロパティ( "ro.kernel.qemu.gles" 、 null )、新しいプロパティ( "ro.kernel.qemu" 、 "1" )、
- 新しいプロパティ( "ro.product.device" 、 "generic" )、新しいプロパティ( "ro.product.model" 、 "sdk" )、
- 新しいプロパティ ( "ro.product.name" 、 "sdk" )、
- 新しいプロパティ( "ro.serialno" 、 null )};
- /**
- * 閾値。いわゆる「既知の」シミュレータ特性は完全に正確ではないため、誤検知の結果が出る可能性があります。そのため、一定の閾値を維持することで検出効果を高めることができます。
- */
- プライベートスタティック MIN_PROPERTIES_THRESHOLD を 0x5 に設定します。
- /**
- * 指定されたシステム プロパティを照会して QEMU 環境を検出し、検出結果をしきい値と比較します。
- *
- * @param context Android アプリケーションの{link Context} オブジェクト。
- *十分なプロパティが見つかった場合は {@code true }を返し、そうでない場合は{@code false }を返します。
- */
- パブリックブール値hasQEmuProps(コンテキストコンテキスト) {
- 見つかったプロパティが 0 である。
-
- (プロパティproperty : known_props) {
- 文字列 property_value = Utilities.getProp(context, property.name );
- // null以外の値を期待しているかどうかを確認します
- if ((property.seek_value == null ) && (property_value != null )) {
- 見つかったプロパティ++;
- }
- //シークする値が期待されているかどうかを確認します
- ((property.seek_value != null ) && (property_value.indexOf(property.seek_value) != -1)) の場合 {
- 見つかったプロパティ++;
- }
-
- }
-
- 見つかったプロパティがMIN_PROPERTIES_THRESHOLD以上の場合
- 戻る 真実;
- }
-
- 戻る 間違い;
- }
これらは、何らかの経験と機能に基づいて比較される属性です。ここでの属性と、後続のファイル属性の一部については詳しく説明しません。 デバイスID - private static String[] known_device_ids = { "0000000000000000" , //デフォルトのエミュレータID
- "e21833235b6eef10" , // VirusTotal ID
- "012345678912345" };
- 公共 静的ブール型hasKnownDeviceId(コンテキストコンテキスト) {
- TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-
- 文字列デバイスID = telephonyManager.getDeviceId();
-
- (文字列known_deviceId : known_device_ids) {
- 既知のデバイスIDがデバイスIDと等しい場合、
- 戻る 真実;
- }
-
- }
- 戻る 間違い;
- }
デフォルト番号 - プライベート静的String[] known_numbers = {
- "15555215554" 、 // エミュレータのデフォルトの電話番号 + VirusTotal
- "15555215556" 、 "15555215558" 、 "15555215560" 、 "15555215562" 、 "15555215564" 、 "15555215566" 、
- "15555215568" 、 "15555215570" 、 "15555215572" 、 "15555215574" 、 "15555215576" 、 "15555215578" 、
- "15555215580" , "15555215582" , "15555215584" ,};
- 公共 静的ブール型hasKnownPhoneNumber(コンテキストコンテキスト) {
- TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-
- 文字列 phoneNumber = telephonyManager.getLine1Number();
-
- for (文字列番号:既知の番号) {
- if (number.equalsIgnoreCase(phoneNumber)) {
- 戻る 真実;
- }
-
- }
- 戻る 間違い;
- }
IMSI - private static String[] known_imsi_ids = { "3102600000000000" // デフォルトのIMSI番号
- };
- 公共 静的ブール型hasKnownImsi(コンテキストコンテキスト) {
- TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- 文字列 imsi = telephonyManager.getSubscriberId();
-
- (文字列 known_imsi : known_imsi_ids)の場合{
- (既知のimsi.equalsIgnoreCase(imsi)の場合){
- 戻る 真実;
- }
- }
- 戻る 間違い;
- }
ビルドクラス - 公共 静的ブール型hasEmulatorBuild(コンテキストコンテキスト) {
- 文字列 BOARD = android.os.Build.BOARD; //名前 基盤となるボードの "未知" 。
- // これはよく起こるようです 本物のハードウェア...それは悲しい
- // 文字列 BOOTLOADER = android.os.Build.BOOTLOADER; // システム ブートローダーのバージョン番号。
- 文字列 BRAND = android.os.Build.BRAND; // ソフトウェアがカスタマイズされているブランド (例: キャリア) (存在する場合)。
- // "ジェネリック"
- 文字列 DEVICE = android.os.Build.DEVICE; //名前 工業デザインの。 "ジェネリック"
- 文字列 HARDWARE = android.os.Build.HARDWARE; //名前 ハードウェア(カーネルコマンドラインからまたは
- ///proc)。 "金魚"
- 文字列 MODEL = android.os.Build.MODEL; //エンドユーザーに表示される名前 最終製品用。 「SDK」
- 文字列 PRODUCT = android.os.Build.PRODUCT; //名前 製品全体の。
- if ((BOARD.compareTo( "不明" ) == 0) /* || (BOOTLOADER.compareTo( "不明" ) == 0) */
- || (BRAND.compareTo( "generic" ) == 0) || (DEVICE.compareTo( "generic" ) == 0)
- || (MODEL.compareTo( "sdk" ) == 0) || (PRODUCT.compareTo( "sdk" ) == 0)
- || (HARDWARE.compareTo( "金魚" ) == 0)) {
- 戻る 真実;
- }
- 戻る 間違い;
- }
オペレーター名 - 公共 静的ブール値isOperatorNameAndroid(コンテキストparamContext) {
- 文字列 szOperatorName = ((TelephonyManager) paramContext.getSystemService(Context.TELEPHONY_SERVICE)).getNetworkOperatorName();
- ブール値 isAndroid = szOperatorName.equalsIgnoreCase( "android" );
- isAndroidを返します。
- }
QEMU ドライバー - プライベート静的String[] known_qemu_drivers = { "金魚" };
- /**
- * ドライバファイルを読み込み、既知のqemuドライバが含まれているかどうかを確認します
- *
- *既知のドライバーが存在することが判明した場合は{@code true }を返し、そうでない場合は{@code false }を返します。
- */
- 公共 静的ブール型hasQEmuDrivers() {
- for (ファイル drivers_file : 新しいファイル[]{新しいファイル( "/proc/tty/drivers" ), 新しいファイル( "/proc/cpuinfo" )}) {
- drivers_file.exists() と drivers_file.canRead() が存在する場合 {
- // 気にしない 私たちが関心のある情報はここにあるはずなので、過去のものをたくさん読んでください
- バイト[]データ = 新しいバイト[1024];
- 試す {
- InputStreamは= new FileInputStream(drivers_file);です。
- は。読み取り( データ );
- は。近い();
- } catch (例外例外) {
- 例外.printStackTrace();
- }
-
- 文字列driver_data = 新しい文字列(data);
- (文字列known_qemu_driver : FindEmulator.known_qemu_drivers) {
- (driver_data.indexOf(known_qemu_driver)!= -1)の場合{
- 戻る 真実;
- }
- }
- }
- }
-
- 戻る 間違い;
- }
QEMU ファイル - プライベート静的String[] known_files = { "/system/lib/libc_malloc_debug_qemu.so" 、 "/sys/qemu_trace" 、
- "/system/bin/qemu-props" };
- /**
- * 既知のQEMU環境ファイルが存在するかどうかを確認する
- *
- *ファイルが存在することがわかった場合は{@code true }を返し、そうでない場合は{@code false }を返します。
- */
- 公共 静的ブール型hasQEmuFiles() {
- for (文字列パイプ: known_files) {
- ファイル qemu_file = new File(pipe);
- (qemu_fileが存在する場合){
- 戻る 真実;
- }
- }
-
- 戻る 間違い;
- }
Genymotion ファイル - private static String[] known_geny_files = { "/dev/socket/genyd" , "/dev/socket/baseband_genyd" };
- /**
- * 既知のGenemytion環境ファイルがあるかどうかを確認します
- *
- *ファイルが存在することがわかった場合は{@code true }を返し、そうでない場合は{@code false }を返します。
- */
- 公共 静的ブール型hasGenyFiles() {
- for (文字列ファイル: known_geny_files) {
- ファイル geny_file = new File(file);
- geny_fileが存在する場合(){
- 戻る 真実;
- }
- }
-
- 戻る 間違い;
- }
QEMU パイプ - プライベート静的String[] known_pipes = { "/dev/socket/qemud" 、 "/dev/qemu_pipe" };
- /**
- * QEMUで使用されている既知のパイプがあるかどうかを確認します
- *
- *パイプが存在することがわかった場合は{@code true }を返し、存在しない場合は{@code false }を返します。
- */
- 公共 静的ブール型hasPipes() {
- for (文字列パイプ: known_pipes) {
- ファイル qemu_socket = new File(pipe);
- (qemu_socketが存在する場合){
- 戻る 真実;
- }
- }
-
- 戻る 間違い;
- }
ブレークポイントの設定 - 静的{
- // これは 腕のみ有効
- System.loadLibrary( "anti" );
- }
- パブリックネイティブ静的 qemuBkpt()関数
-
- 公共 静的ブール値 checkQemuBreakpoint() {
- ブール型 hit_breakpoint = false ;
-
- // おそらく、これが特定の値であるかどうかを確認したいかもしれません
- int結果 = qemuBkpt();
-
- 結果 > 0 の場合 {
- ヒットブレークポイント = true ;
- }
-
- hit_breakpointを返します。
- }
以下は対応するC++コードです。 - void handler_sigtrap( int Signo) {
- 終了(-1);
- }
-
- void handler_sigbus( intsigno ) {
- 終了(-1);
- }
-
- intセットアップシグナルトラップ() {
- // BKPT はNexus 5 / OnePlus One (およびほとんどのデバイス)でSIGTRAP をスローします
- シグナル(SIGTRAP、handler_sigtrap);
- // BKPT はNexus 4にSIGBUS をスローします
- シグナル(SIGBUS、handler_sigbus);
- }
-
- // これによりSIGSEGVが発生します QEMUや適切に尊重される
- int tryBKPT() {
- __asm__ __volatile__ ( "bkpt 255" );
- }
-
- jint Java_diff_strazzere_anti_emulator_FindEmulator_qemuBkpt(JNIEnv* env, jobject jObject) {
-
- pid_t 子 = fork();
- int child_status、ステータス = 0;
-
- if(子 == 0) {
- セットアップSigTrap();
- BKPT() を試行します。
- }そうでない場合(子 == -1) {
- ステータス = -1;
- }それ以外{
-
- 整数タイムアウト = 0;
- 整数i = 0;
- while ( waitpid(child, &child_status, WNOHANG) == 0 ) {
- 睡眠(1);
- // ここで時間を調整することもできますが、私の経験では、子供がすぐに戻ってこない場合は
- //何か問題が発生し、エミュレートされたデバイスである
- i++ == 1の場合
- タイムアウト = 1;
- 壊す;
- }
- }
-
- タイムアウト == 1 の場合
- // プロセスがタイムアウトしました- エミュレートされたデバイスと子がフリーズしている可能性があります
- ステータス = 1;
- }
-
- if (WIFEXITED(child_status)) {
- // 子プロセスは正常に終了する
- ステータス = 0;
- }それ以外{
- // 正常に終了しませんでした - エミュレータである可能性が高いです
- ステータス = 2;
- }
-
- // 子が死んでいることを確認する
- 子を殺します。
- }
-
- ステータスを返します。
- }
関連する情報が見つからなかったため、ここでの説明は正確ではない可能性があります。私は自分の理解に基づいてのみそれを説明することができます: SIGTRAP は、デバッガーがブレークポイントを設定したときに生成される信号です。これは、nexus5 や OnePlus フォンなど、ほとんどのフォンでトリガーできます。 SIGBUS はバス エラーです。ポインタは有効なアドレスにアクセスできますが、データの不整合などの理由によりバスは使用できません。 Nexus4 フォンでトリガーできます。 Bkpt は arm のブレークポイント命令です。これは qemu が提起した問題です。 SIGSEGV 信号により qemu がクラッシュします。著者はこのクラッシュを利用して qemu を検出したいと考えています。プログラムが正常に終了しなかったり、フリーズしたりする場合は、エミュレータに問題がある可能性が高いと判断できます。 アジア開発銀行 - 公共 静的ブール型hasEmulatorAdb() {
- 試す {
- FindDebugger.hasAdbInEmulator()を返します。
- } catch (例外例外) {
- 例外.printStackTrace();
- 戻る 間違い;
- }
- }
isUserAMonkey() - 公共 静的ブール型hasEmulatorAdb() {
- 試す {
- FindDebugger.hasAdbInEmulator()を返します。
- } catch (例外例外) {
- 例外.printStackTrace();
- 戻る 間違い;
- }
- }
これは実際には、現在の操作がユーザーによって要求されたのか、スクリプトによって要求されたのかを検出するために使用されます。 デバッガが接続されているかどうか - /**
- * 信じられないかもしれませんが、この方法を使用する強化プログラムは数多くあります...
- */
- 公共 静的ブール値isBeingDebugged() {
- Debug.isDebuggerConnected()を返します。
- }
このメソッドは、デバッグを検出し、デバッガーが接続されているかどうかを判断するために使用されます。 ptrace - プライベート静的文字列 tracerpid = "TracerPid" ;
- /**
- * アリババはこれを利用して、申請プロセスが追跡されているかどうかを検出します
- *
- * 回避は簡単です。使用方法は、3秒ごとにチェックするスレッドを作成することです。検出された場合は、プログラムがクラッシュします。
- *
- * @戻る
- * @throwsIOException 例外をスローします
- */
- 公共 静的ブール型hasTracerPid()はIOExceptionをスローします{
- BufferedReader リーダー = null ;
- 試す {
- リーダー = 新しい BufferedReader(新しい InputStreamReader(新しい FileInputStream( "/proc/self/status" )), 1000);
- 文字列;
-
- ((line = reader.readLine()) != null ) の場合 {
- (line.length() > tracerpid.length()) の場合 {
- (行のサブストリング(0、tracerpid.length())。equalsIgnoreCase(tracerpid)){
- 整数.decode(line.substring ( tracerpid.length() + 1).trim()) > 0 の場合
- 戻る 真実;
- }
- 壊す;
- }
- }
- }
-
- } catch (例外例外) {
- 例外.printStackTrace();
- ついに
- リーダーを閉じる() ;
- }
- 戻る 間違い;
- }
この方法は、/proc/self/status 内の TracerPid 項目を確認することです。この項目は、トレースがない場合にはデフォルトで 0 に設定され、プログラムがトレースされている場合は対応する pid に変更されます。したがって、TracerPid が 0 でない場合は、シミュレータ環境にあると見なすことができます。 TCP接続 - 公共 静的ブール型hasAdbInEmulator()はIOExceptionをスローします{
- ブール値 adbInEmulator = false ;
- BufferedReader リーダー = null ;
- 試す {
- リーダー = 新しい BufferedReader(新しい InputStreamReader(新しい FileInputStream( "/proc/net/tcp" )), 1000);
- 文字列;
- //列名をスキップ
- リーダー.readLine();
-
- ArrayList<tcp> tcpList = 新しいArrayList<tcp>();
-
- ((line = reader.readLine()) != null ) の場合 {
- tcpリスト。追加(tcp.create ( line.split( "\\W+" )));
- }
-
- リーダーを閉じる() ;
-
- // Adbは常に0.0.0.0にバウンスされますが、ポートは変更される可能性があります
- //実際のデバイスは != 127.0.0.1 である必要があります
- 整数adbPort = -1;
- (tcp tcpItem : tcpList)の場合{
- tcpItem.localIp == 0 の場合 {
- adbPort = tcpItem.localPort;
- 壊す;
- }
- }
-
- (adbPort != -1) の場合 {
- (tcp tcpItem : tcpList)の場合{
- tcpItem.localIp が 0 の場合、tcpItem.localPort は adbPort になります。
- adbInEmulator = true ;
- }
- }
- }
- } catch (例外例外) {
- 例外.printStackTrace();
- ついに
- リーダーを閉じる() ;
- }
-
- adbInEmulatorを返します。
- }
-
- 公共 静的クラスtcp {
-
- 公共 整数ID;
- パブリックlong localIp;
- 公共 ローカルポート番号。
- 公共 リモートIPアドレス。
- 公共 intリモートポート;
-
- 静的tcp作成(String[]パラメータ){
- 新しいtcp(params[1]、params[2]、params[3]、params[4]、params[5]、params[6]、params[7]、params[8]、
- パラメータ[9]、パラメータ[10]、パラメータ[11]、パラメータ[12]、パラメータ[13]、パラメータ[14]);
- }
-
- パブリックtcp(文字列id、文字列localIp、文字列localPort、文字列remoteIp、文字列remotePort、文字列state、
- 文字列 tx_queue、文字列 rx_queue、文字列 tr、文字列 tm_when、文字列 retrnsmt、文字列 uid、
- 文字列タイムアウト、文字列inode) {
- this.id =整数.parseInt(id, 16);
- this.localIp = Long.parseLong(localIp, 16);
- this.localPort = Integer .parseInt(localPort, 16);
- }
- }
この方法は、/proc/net/tcp の情報を読み取って adb が存在するかどうかを判定します。例えば、実機の情報は 0:4604D20A:B512 A3D13AD8... ですが、シミュレーター上の対応する情報は 0:00000000:0016 00000000:0000 です。これは、adb が通常 IP アドレス 0.0.0.0 に反映されるためです。ポートは変更される可能性がありますが、確かに実現可能です。 テインドロイド - 公共 静的ブール型hasPackageNameInstalled(コンテキストコンテキスト、文字列パッケージ名) {
- パッケージマネージャー packageManager = context.getPackageManager();
-
- // 理論的には、パッケージインストーラが例外をスローしない場合は、パッケージが存在します
- 試す {
- packageManager.getInstallerPackageName(パッケージ名);
- 戻る 真実;
- } (IllegalArgumentException 例外をキャッチ) {
- 戻る 間違い;
- }
- }
- 公共 静的ブール型hasAppAnalysisPackage(コンテキストコンテキスト) {
- Utilities.hasPackageNameInstalled(context, "org.appanalysis" )を返します。
- }
- 公共 静的ブール型hasTaintClass() {
- 試す {
- クラス.forName( "dalvik.system.Taint" );
- 戻る 真実;
- }
- (ClassNotFoundException 例外をキャッチ) {
- 戻る 間違い;
- }
- }
これは比較的簡単です。パッケージ名とTaintクラスを検出して、汚染解析ツールTaintDroidがインストールされているかどうかを判定します。さらに、TaintDroid のいくつかのメンバー変数を検出することもできます。 eth0 - プライベート静的ブール値hasEth0Interface() {
- 試す {
- ( Enumeration <NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
- NetworkInterface intf = en.nextElement();
- (intf.getName().equals( "eth0" ))の場合
- 戻る 真実;
- }
- } catch (SocketException ex) {
- }
- 戻る 間違い;
- }
eth0 ネットワーク カードが存在するかどうかを確認します。 センサー 携帯電話にはさまざまなセンサーが搭載されていますが、本質的には環境から収集した情報に基づいて値を出力するため、センサーをシミュレートするのは非常に困難です。これらのセンサーは、携帯電話やシミュレーターを識別するための新たな機会を提供します。 たとえば、論文「Rage Against the Virtual Machine: Hindering Dynamic Analysis of Android Malware」では、著者らが Android エミュレーターの加速度計をテストし、Android エミュレーターのセンサーが同じ時間間隔内に同じ値を生成することを発見しました (観測結果は 0.8 秒、標準偏差は 0.003043 でした)。明らかに、現実世界のセンサーではこれは不可能です。 まずセンサーリスナーを登録します。登録に失敗した場合は、シミュレーター側の問題である可能性があります(実機がセンサーに対応していない可能性は除く)。登録が成功した場合は、onSensorChanged コールバック メソッドを確認します。このメソッドを連続して呼び出した際に観測されたセンサー値や時間間隔が同じであれば、シミュレータ環境であると判断できます。 QEMU タスクスケジューリング パフォーマンス上の理由から、QEMU は命令を実行するたびにプログラム カウンター (PC) を積極的に更新しません。変換された命令はローカルで実行されるため、PC を増やすと追加の命令が必要になり、オーバーヘッドが発生します。したがって、QEMU は線形実行プロセスを中断する命令 (分岐命令など) を実行するときにのみプログラム カウンターを更新します。 つまり、いくつかの基本ブロックの実行中にスケジューリング イベントが発生した場合、スケジューリング前に PC を復元する方法はありません。このため、QEMU は基本ブロックの実行後にのみイベントをスケジュールし、実行中にはスケジュールしません。 上図に示すように、スケジューリングはいつでも発生する可能性があるため、シミュレータ以外の環境では多数のスケジューリング ポイントが観察されます。シミュレータ環境では、特定のスケジュール ポイントのみが表示されます。 SMC 識別 QEMU はコード ページの変更を追跡するため、自己変更コード (SMC) を使用して QEMU をインストルメント化し、エミュレーターと実際のデバイス間の実行フローを変更する新しい方法があります。 ARM プロセッサには、命令アクセス用 (I キャッシュ) とデータ アクセス用 (D キャッシュ) の 2 つの異なるバッファ キャッシュが含まれています。しかし、ARM などのハーバード アーキテクチャでは、I-Cache と D-Cache 間の一貫性を保証することはできません。したがって、新しいコードがメイン メモリに書き込まれた後でも、CPU が古い (おそらく無効な) コードを実行する可能性があります。 この問題は、2 つのキャッシュを強制的に整合させることで解決できます。これには次の 2 つの手順があります。 - メインメモリをクリーンアップして、Dキャッシュに新しく書き込まれたコードをメインメモリに移動できるようにします。
- I-Cacheを無効にして、メインメモリから新しい内容で補充できるようにします。
ネイティブ Android コードでは、システム コールを通じて上記の操作を完了する cacheflush 関数を使用できます。 読み取り/書き込み権限を持つメモリを使用して、2 つの異なる関数 f1 と f2 のコードを含むコードを識別します。これら 2 つの機能は実は非常に単純です。それぞれの関数名をグローバル文字列変数の末尾に追加するだけです。これら 2 つの関数はループ内でインターリーブされるため、結果の文字列から関数呼び出しシーケンスを推測できます。 前述したように、キャッシュを同期するには cacheflush を呼び出します。実際のデバイスとシミュレーターでコードを実行すると、同じ結果が得られます。各実行で一貫した関数呼び出しのシーケンスが生成されます。 次に、cacheflush の呼び出しを削除し、同じ操作を実行します。実際のデバイスでは、実行するたびに関数呼び出しのランダムなシーケンスが観察されます。これは、I-Cache に古い命令が含まれている可能性があり、前述のように、呼び出されるたびにキャッシュが同期されなくなるためです。 これはシミュレータ環境では発生せず、関数呼び出しシーケンスは cacheflush が削除される前とまったく同じになります。つまり、各関数呼び出しの前にキャッシュは一貫しています。これは、QEMU がコード ページの変更を追跡し、生成されたコードが常にメモリ内のターゲット命令と一致するようにするため、QEMU は以前のバージョンのコード変換を破棄し、新しいコードを再生成するためです。 結論 これを読んで、検出方法が十分あると思われるかもしれませんが、私が読んだのは2013年と2014年の情報だけです。近年に関するデータは含まれていません。 ***これらの検出方法をマインドマップ(添付ファイルを参照)に統合して、誰でも見られるようにします。私とコミュニケーションを取り、指導してください。 参考リンク strazzere/anti-emulator: ***2013 年の HitCon で公開され、仮想マシンを検出するためのいくつかの方法とアイデアを提案しました。これは Android エミュレータ検出の先駆的な作業となるはずです。この記事は主にこのリポジトリに基づいています。 Rage Against the Virtual Machine: Android マルウェアの動的分析の妨害: タスク スケジューリングによる検出と SMC を使用した識別は、どちらもこの論文から引用されています。この論文と次の論文は参考として非常に価値があり、読む価値があります。 サンドボックス検出による Android ランタイム分析の回避: この論文では、Android ランタイム環境を検出するための多くの方法とアイデアを提案しています。内容が豊富で非常に包括的であり、読む価値もあります。 CalebFenton/AndroidEmulatorDetect: このリポジトリは、実際にいくつかの記事やリポジトリの検出方法とコードを統合しており、包括的ではありませんが、多くの参照リンクが提供されており、手がかりをたどることができました。 Android アプリケーションがエミュレータで実行されているかどうかをどのように検出できますか?ネットユーザーからは多くの解決策が提示されている。しかし実際には、それらは包括的なものではなく、エミュレータ検出における氷山の一角にすぎません。結局のところ、検出できる場所はたくさんあります。 タスク スケジューリング機能を使用して Android エミュレーターを検出する |