Skip to content

MASTG-DEMO-0116: Native Anti-Debugging Checks with TracerPid and ptrace

Download MASTG-DEMO-0116 APK Open MASTG-DEMO-0116 Folder Build MASTG-DEMO-0116 APK

Sample

This sample demonstrates native anti-debugging checks that inspect TracerPid in /proc/self/status and use a ptrace self-check flow.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package org.owasp.mastestapp

import android.content.Context
import android.os.Build
import android.system.OsConstants
import java.io.File

class MastgTest (private val context: Context){

    companion object {
        private var nativeLibraryLoadError: String? = null

        private val nativeLibraryLoaded: Boolean = try {
            System.loadLibrary("tracerpidcheck")
            true
        } catch (t: Throwable) {
            nativeLibraryLoadError = t.message ?: t.toString()
            false
        }
    }

    fun mastgTest(): String {
        // SUMMARY: This sample checks /proc/self/status TracerPid from Kotlin and from a native library.
        val r = DemoResults("0x41")

        try {
            val tracerPidKotlin = getTracerPidFromProcStatus()

            if (tracerPidKotlin > 0) {
                // FAIL: [MASTG-TEST-0352] TracerPid is non-zero, indicating ptrace-based native debugging.
                r.add(
                    Status.FAIL,
                    "Kotlin check: native debugger detected via TracerPid=$tracerPidKotlin in /proc/self/status."
                )
            } else {
                // PASS: [MASTG-TEST-0352] TracerPid is zero, indicating no ptrace-based native debugger is attached.
                r.add(
                    Status.PASS,
                    "Kotlin check: no native debugger detected via TracerPid=$tracerPidKotlin in /proc/self/status."
                )
            }
        } catch (e: IllegalStateException) {
            r.add(Status.ERROR, "Kotlin check failed: ${e.message ?: e.toString()}")
        } catch (e: NumberFormatException) {
            r.add(Status.ERROR, "Kotlin check failed: could not parse TracerPid in /proc/self/status: ${e.message}")
        } catch (e: Exception){
            r.add(Status.ERROR, "Kotlin check failed: unexpected error while checking TracerPid: ${e.message}")
        }

        if (!nativeLibraryLoaded) {
            r.add(Status.ERROR, "Native check failed: could not load tracerpidcheck library: $nativeLibraryLoadError")
            return r.toJson()
        }

        try {
            val tracerPidNative = getTracerPidNative()

            if (tracerPidNative < 0) {
                r.add(Status.ERROR, "Native check failed: could not read or parse TracerPid from /proc/self/status.")
            } else if (tracerPidNative > 0) {
                // FAIL: [MASTG-TEST-0352] Native TracerPid check detected ptrace-based debugging.
                r.add(
                    Status.FAIL,
                    "Native check: native debugger detected via TracerPid=$tracerPidNative in /proc/self/status."
                )
            } else {
                // PASS: [MASTG-TEST-0352] Native TracerPid check did not detect ptrace-based debugging.
                r.add(
                    Status.PASS,
                    "Native check: no native debugger detected via TracerPid=$tracerPidNative in /proc/self/status."
                )
            }
        } catch (e: Exception) {
            r.add(Status.ERROR, "Native check failed: unexpected error while checking TracerPid: ${e.message}")
        }

        try {
            val tracerPidInline = getTracerPidInlineSyscallNative()
            if (tracerPidInline == -2) {
                r.add(
                    Status.ERROR,
                    "Inline-syscall native check is unsupported on this ABI (${Build.SUPPORTED_ABIS.joinToString()})."
                )
            } else if (tracerPidInline < 0) {
                r.add(Status.ERROR, "Inline-syscall native check failed: could not read or parse TracerPid.")
            } else if (tracerPidInline > 0) {
                // FAIL: [MASTG-TEST-0352] Inline-syscall native TracerPid check detected ptrace-based debugging.
                r.add(
                    Status.FAIL,
                    "Inline-syscall native check: native debugger detected via TracerPid=$tracerPidInline in /proc/self/status."
                )
            } else {
                // PASS: [MASTG-TEST-0352] Inline-syscall native TracerPid check did not detect ptrace-based debugging.
                r.add(
                    Status.PASS,
                    "Inline-syscall native check: no native debugger detected via TracerPid=$tracerPidInline in /proc/self/status."
                )
            }
        } catch (e: Exception) {
            r.add(Status.ERROR, "Inline-syscall native check failed: unexpected error while checking TracerPid: ${e.message}")
        }

        try {
            val ptraceSelf = ptraceSelfDetectNative()
            if (ptraceSelf > 0) {
                // FAIL: [MASTG-TEST-0352] Native ptrace self-check indicates a ptrace-based debugger is attached.
                r.add(
                    Status.FAIL,
                    "Native ptrace self-check: debugger likely detected (child could not PTRACE_SEIZE parent: EPERM)."
                )
            } else if (ptraceSelf == 0) {
                // PASS: [MASTG-TEST-0352] Native ptrace self-check did not detect a ptrace-based debugger.
                r.add(
                    Status.PASS,
                    "Native ptrace self-check: no ptrace-based debugger detected (child seized parent)."
                )
            } else {
                val errno = -ptraceSelf
                val errnoName = errnoName(errno)
                r.add(
                    Status.ERROR,
                    "Native ptrace self-check failed: ptrace seize flow returned errno=$errno ($errnoName)."
                )
            }
        } catch (e: Exception) {
            r.add(Status.ERROR, "Native ptrace self-check failed: unexpected error: ${e.message}")
        }

        return r.toJson()
    }

    private external fun getTracerPidNative(): Int
    private external fun getTracerPidInlineSyscallNative(): Int
    private external fun ptraceSelfDetectNative(): Int

    private fun getTracerPidFromProcStatus(): Int {
        val statusText = File("/proc/self/status").readText()
        val tracerPidLine = statusText
            .lineSequence()
            .firstOrNull { it.startsWith("TracerPid:") }
            ?: throw IllegalStateException("TracerPid line not found in /proc/self/status")

        val value = tracerPidLine.substringAfter(":").trim()
        if (value.isEmpty()) {
            throw IllegalStateException("TracerPid value is empty in /proc/self/status")
        }

        return value.toInt()
    }

    private fun errnoName(errno: Int): String {
        return when (errno) {
            OsConstants.EPERM -> "EPERM"
            OsConstants.ESRCH -> "ESRCH"
            OsConstants.ECHILD -> "ECHILD"
            OsConstants.EBUSY -> "EBUSY"
            OsConstants.EINVAL -> "EINVAL"
            OsConstants.ENOSYS -> "ENOSYS"
            OsConstants.EACCES -> "EACCES"
            else -> "UNKNOWN_ERRNO"
        }
    }

}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package org.owasp.mastestapp;

import android.content.Context;
import android.os.Build;
import android.system.OsConstants;
import java.io.File;
import java.util.Iterator;
import kotlin.Metadata;
import kotlin.collections.ArraysKt;
import kotlin.io.FilesKt;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.internal.Intrinsics;
import kotlin.sequences.Sequence;
import kotlin.text.StringsKt;

/* compiled from: MastgTest.kt */
@Metadata(d1 = {"\u0000 \n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\b\n\u0002\b\u0007\b\u0007\u0018\u0000 \u000f2\u00020\u0001:\u0001\u000fB\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\u0006\u0010\u0006\u001a\u00020\u0007J\t\u0010\b\u001a\u00020\tH\u0082 J\t\u0010\n\u001a\u00020\tH\u0082 J\t\u0010\u000b\u001a\u00020\tH\u0082 J\b\u0010\f\u001a\u00020\tH\u0002J\u0010\u0010\r\u001a\u00020\u00072\u0006\u0010\u000e\u001a\u00020\tH\u0002R\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\u0010"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "<init>", "(Landroid/content/Context;)V", "mastgTest", "", "getTracerPidNative", "", "getTracerPidInlineSyscallNative", "ptraceSelfDetectNative", "getTracerPidFromProcStatus", "errnoName", "errno", "Companion", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48)
/* loaded from: classes3.dex */
public final class MastgTest {
    private static String nativeLibraryLoadError;
    private static final boolean nativeLibraryLoaded;
    private final Context context;
    public static final int $stable = 8;

    private final native int getTracerPidInlineSyscallNative();

    private final native int getTracerPidNative();

    private final native int ptraceSelfDetectNative();

    public MastgTest(Context context) {
        Intrinsics.checkNotNullParameter(context, "context");
        this.context = context;
    }

    static {
        boolean z;
        try {
            System.loadLibrary("tracerpidcheck");
            z = true;
        } catch (Throwable t) {
            String message = t.getMessage();
            if (message == null) {
                message = t.toString();
            }
            nativeLibraryLoadError = message;
            z = false;
        }
        nativeLibraryLoaded = z;
    }

    /* JADX WARN: Unsupported multi-entry loop pattern (BACK_EDGE: B:30:0x011f -> B:55:0x013c). Please report as a decompilation issue!!! */
    public final String mastgTest() {
        DemoResults r = new DemoResults("0x41");
        try {
            int tracerPidKotlin = getTracerPidFromProcStatus();
            if (tracerPidKotlin > 0) {
                r.add(Status.FAIL, "Kotlin check: native debugger detected via TracerPid=" + tracerPidKotlin + " in /proc/self/status.");
            } else {
                r.add(Status.PASS, "Kotlin check: no native debugger detected via TracerPid=" + tracerPidKotlin + " in /proc/self/status.");
            }
        } catch (IllegalStateException e) {
            Status status = Status.ERROR;
            String message = e.getMessage();
            if (message == null) {
                message = e.toString();
            }
            r.add(status, "Kotlin check failed: " + message);
        } catch (NumberFormatException e2) {
            r.add(Status.ERROR, "Kotlin check failed: could not parse TracerPid in /proc/self/status: " + e2.getMessage());
        } catch (Exception e3) {
            r.add(Status.ERROR, "Kotlin check failed: unexpected error while checking TracerPid: " + e3.getMessage());
        }
        if (!nativeLibraryLoaded) {
            r.add(Status.ERROR, "Native check failed: could not load tracerpidcheck library: " + nativeLibraryLoadError);
            return r.toJson();
        }
        try {
            int tracerPidNative = getTracerPidNative();
            if (tracerPidNative < 0) {
                r.add(Status.ERROR, "Native check failed: could not read or parse TracerPid from /proc/self/status.");
            } else if (tracerPidNative > 0) {
                r.add(Status.FAIL, "Native check: native debugger detected via TracerPid=" + tracerPidNative + " in /proc/self/status.");
            } else {
                r.add(Status.PASS, "Native check: no native debugger detected via TracerPid=" + tracerPidNative + " in /proc/self/status.");
            }
        } catch (Exception e4) {
            r.add(Status.ERROR, "Native check failed: unexpected error while checking TracerPid: " + e4.getMessage());
        }
        try {
            int tracerPidInline = getTracerPidInlineSyscallNative();
            if (tracerPidInline == -2) {
                Status status2 = Status.ERROR;
                String[] SUPPORTED_ABIS = Build.SUPPORTED_ABIS;
                Intrinsics.checkNotNullExpressionValue(SUPPORTED_ABIS, "SUPPORTED_ABIS");
                r.add(status2, "Inline-syscall native check is unsupported on this ABI (" + ArraysKt.joinToString$default(SUPPORTED_ABIS, (CharSequence) null, (CharSequence) null, (CharSequence) null, 0, (CharSequence) null, (Function1) null, 63, (Object) null) + ").");
            } else if (tracerPidInline < 0) {
                r.add(Status.ERROR, "Inline-syscall native check failed: could not read or parse TracerPid.");
            } else if (tracerPidInline > 0) {
                r.add(Status.FAIL, "Inline-syscall native check: native debugger detected via TracerPid=" + tracerPidInline + " in /proc/self/status.");
            } else {
                r.add(Status.PASS, "Inline-syscall native check: no native debugger detected via TracerPid=" + tracerPidInline + " in /proc/self/status.");
            }
        } catch (Exception e5) {
            r.add(Status.ERROR, "Inline-syscall native check failed: unexpected error while checking TracerPid: " + e5.getMessage());
        }
        try {
            int ptraceSelf = ptraceSelfDetectNative();
            if (ptraceSelf > 0) {
                r.add(Status.FAIL, "Native ptrace self-check: debugger likely detected (child could not PTRACE_SEIZE parent: EPERM).");
            } else if (ptraceSelf == 0) {
                r.add(Status.PASS, "Native ptrace self-check: no ptrace-based debugger detected (child seized parent).");
            } else {
                int errno = -ptraceSelf;
                String errnoName = errnoName(errno);
                r.add(Status.ERROR, "Native ptrace self-check failed: ptrace seize flow returned errno=" + errno + " (" + errnoName + ").");
            }
        } catch (Exception e6) {
            r.add(Status.ERROR, "Native ptrace self-check failed: unexpected error: " + e6.getMessage());
        }
        return r.toJson();
    }

    private final int getTracerPidFromProcStatus() {
        Object element$iv;
        String statusText = FilesKt.readText$default(new File("/proc/self/status"), null, 1, null);
        Sequence $this$firstOrNull$iv = StringsKt.lineSequence(statusText);
        Iterator<String> it = $this$firstOrNull$iv.iterator();
        while (true) {
            if (it.hasNext()) {
                element$iv = it.next();
                String it2 = (String) element$iv;
                if (StringsKt.startsWith$default(it2, "TracerPid:", false, 2, (Object) null)) {
                    break;
                }
            } else {
                element$iv = null;
                break;
            }
        }
        String tracerPidLine = (String) element$iv;
        if (tracerPidLine == null) {
            throw new IllegalStateException("TracerPid line not found in /proc/self/status");
        }
        String value = StringsKt.trim((CharSequence) StringsKt.substringAfter$default(tracerPidLine, ":", (String) null, 2, (Object) null)).toString();
        if (value.length() == 0) {
            throw new IllegalStateException("TracerPid value is empty in /proc/self/status");
        }
        return Integer.parseInt(value);
    }

    private final String errnoName(int errno) {
        return errno == OsConstants.EPERM ? "EPERM" : errno == OsConstants.ESRCH ? "ESRCH" : errno == OsConstants.ECHILD ? "ECHILD" : errno == OsConstants.EBUSY ? "EBUSY" : errno == OsConstants.EINVAL ? "EINVAL" : errno == OsConstants.ENOSYS ? "ENOSYS" : errno == OsConstants.EACCES ? "EACCES" : "UNKNOWN_ERRNO";
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <jni.h>

#include <cstdio>
#include <cstring>

extern "C" JNIEXPORT jint JNICALL
Java_org_owasp_mastestapp_MastgTest_getTracerPidNative(JNIEnv* env, jobject thiz) {
    (void)env;
    (void)thiz;

    FILE* file = std::fopen("/proc/self/status", "r");
    if (file == nullptr) {
        return -1;
    }

    char line[256];
    while (std::fgets(line, sizeof(line), file) != nullptr) {
        if (std::strncmp(line, "TracerPid:", 10) == 0) {
            int tracer_pid = -1;
            const int parsed = std::sscanf(line, "TracerPid:\t%d", &tracer_pid);
            std::fclose(file);
            if (parsed == 1) {
                return tracer_pid;
            }
            return -1;
        }
    }

    std::fclose(file);
    return -1;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <jni.h>

#include <cerrno>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <unistd.h>

extern "C" JNIEXPORT jint JNICALL
Java_org_owasp_mastestapp_MastgTest_ptraceSelfDetectNative(JNIEnv *env, jobject thiz)
{
    (void)env;
    (void)thiz;

    const pid_t child_pid = fork();
    if (child_pid < 0)
    {
        return 2;
    }

    if (child_pid == 0)
    {
        const pid_t parent_pid = getppid();

        errno = 0;
        if (ptrace(PTRACE_SEIZE, parent_pid, nullptr, nullptr) == 0)
        {
            _exit(0);
        }

        if (errno == EPERM)
        {
            _exit(1);
        }

        _exit(2);
    }

    int status = 0;
    if (waitpid(child_pid, &status, 0) < 0)
    {
        return 2;
    }

    if (!WIFEXITED(status))
    {
        return 2;
    }

    const int code = WEXITSTATUS(status);
    if (code == 0 || code == 1 || code == 2)
    {
        return code;
    }

    return 2;
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include <jni.h>

#include <fcntl.h>

#include <cstddef>
#include <cstdlib>
#include <cstring>

namespace {

constexpr int kError = -1;
constexpr int kUnsupportedArch = -2;

#if defined(__aarch64__)
constexpr long kNrOpenat = 56;
constexpr long kNrClose = 57;
constexpr long kNrRead = 63;
#elif defined(__x86_64__)
constexpr long kNrRead = 0;
constexpr long kNrClose = 3;
constexpr long kNrOpenat = 257;
#else
constexpr long kNrOpenat = 0;
constexpr long kNrClose = 0;
constexpr long kNrRead = 0;
#endif

inline long raw_syscall3(long n, long a1, long a2, long a3) {
#if defined(__aarch64__)
    register long x8 __asm__("x8") = n;
    register long x0 __asm__("x0") = a1;
    register long x1 __asm__("x1") = a2;
    register long x2 __asm__("x2") = a3;
    __asm__ volatile("svc #0"
                     : "+r"(x0)
                     : "r"(x8), "r"(x1), "r"(x2)
                     : "memory");
    return x0;
#elif defined(__x86_64__)
    long ret;
    __asm__ volatile("syscall"
                     : "=a"(ret)
                     : "a"(n), "D"(a1), "S"(a2), "d"(a3)
                     : "rcx", "r11", "memory");
    return ret;
#else
    (void)n;
    (void)a1;
    (void)a2;
    (void)a3;
    return kUnsupportedArch;
#endif
}

inline long raw_syscall1(long n, long a1) {
#if defined(__aarch64__)
    register long x8 __asm__("x8") = n;
    register long x0 __asm__("x0") = a1;
    __asm__ volatile("svc #0"
                     : "+r"(x0)
                     : "r"(x8)
                     : "memory");
    return x0;
#elif defined(__x86_64__)
    long ret;
    __asm__ volatile("syscall"
                     : "=a"(ret)
                     : "a"(n), "D"(a1)
                     : "rcx", "r11", "memory");
    return ret;
#else
    (void)n;
    (void)a1;
    return kUnsupportedArch;
#endif
}

int parse_tracer_pid(const char* data) {
    const char* key = "TracerPid:";
    const char* pos = std::strstr(data, key);
    if (pos == nullptr) {
        return kError;
    }

    pos += std::strlen(key);
    while (*pos == ' ' || *pos == '\t') {
        ++pos;
    }

    char* end = nullptr;
    const long parsed = std::strtol(pos, &end, 10);
    if (end == pos || parsed < 0 || parsed > 2147483647L) {
        return kError;
    }

    return static_cast<int>(parsed);
}

int read_tracer_pid_with_inline_syscalls() {
    char buffer[4096] = {0};

    const long fd = raw_syscall3(kNrOpenat,
                                 static_cast<long>(AT_FDCWD),
                                 reinterpret_cast<long>("/proc/self/status"),
                                 static_cast<long>(O_RDONLY));
    if (fd == kUnsupportedArch) {
        return kUnsupportedArch;
    }
    if (fd < 0) {
        return kError;
    }

    const long bytes_read = raw_syscall3(kNrRead,
                                         fd,
                                         reinterpret_cast<long>(buffer),
                                         static_cast<long>(sizeof(buffer) - 1));
    raw_syscall1(kNrClose, fd);

    if (bytes_read <= 0 || bytes_read >= static_cast<long>(sizeof(buffer))) {
        return kError;
    }

    buffer[bytes_read] = '\0';
    return parse_tracer_pid(buffer);
}

}  // namespace

extern "C" JNIEXPORT jint JNICALL
Java_org_owasp_mastestapp_MastgTest_getTracerPidInlineSyscallNative(JNIEnv* env, jobject thiz) {
    (void)env;
    (void)thiz;
    return read_tracer_pid_with_inline_syscalls();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
cmake_minimum_required(VERSION 3.22.1)

project(tracerpidcheck)

add_library(
    tracerpidcheck
    SHARED
    tracerpid_check.cpp
    tracerpid_inline_syscall.cpp
    tracerpid_ptrace_self.cpp
)

find_library(log-lib log)

target_link_libraries(
    tracerpidcheck
    ${log-lib}
)

Steps

Let's run semgrep against the decompiled code and radare2 (iOS) against the compiled native library:

../../../../rules/mastg-android-native-debugger-checks.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
rules:
  - id: mastg-android-native-debugger-checks
    severity: WARNING
    languages: [java]
    metadata:
      summary: Detects Java references to native anti-debugging checks based on TracerPid and ptrace-related indicators.
    message: "[MASVS-RESILIENCE-4] native anti-debugging check indicator detected."
    patterns:
      - pattern-either:
          - pattern: '"/proc/self/status"'
          - pattern: '"TracerPid:"'
          - pattern-regex: PTRACE_(ATTACH|SEIZE)
native_debugger_checks.r2
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
e asm.bytes=false
e scr.color=false
e scr.interactive=false
e asm.var=false
e bin.relocs.apply=true

?e Native anti-debugging strings:
izz~TracerPid,/proc/self/status

?e

?e Imported ptrace symbol:
ii~ptrace

?e

?e Cross-references to ptrace:
axt @ sym.imp.ptrace

?e

?e Disassembly around ptrace references:
pd-- 16 @ `axtq @ sym.imp.ptrace`
run.sh
1
2
3
4
5
#!/usr/bin/env bash
set -euo pipefail

NO_COLOR=true semgrep -c ../../../../rules/mastg-android-native-debugger-checks.yml ./MastgTest_reversed.java --text > output.txt
r2 -q -e bin.relocs.apply=true -e log.quiet=true -i native_debugger_checks.r2 -A libtracerpidcheck.so >> output.txt

Observation

The output contains detections for native anti-debugging indicators, including TracerPid checks in /proc/self/status and ptrace(PTRACE_SEIZE) usage.

output.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
┌─────────────────┐
 3 Code Findings 
└─────────────────┘

    MastgTest_reversed.java
    ❯❱ rules.mastg-android-native-debugger-checks
          [MASVS-RESILIENCE-4] native anti-debugging check indicator detected.

          110 r.add(Status.FAIL, "Native ptrace self-check: debugger likely detected (child could not
               PTRACE_SEIZE parent: EPERM).");
            ⋮┆----------------------------------------
          126 String statusText = FilesKt.readText$default(new File("/proc/self/status"), null, 1, null);
            ⋮┆----------------------------------------
          133 if (StringsKt.startsWith$default(it2, "TracerPid:", false, 2, (Object) null)) {

Native anti-debugging strings:
6   0x00000587 0x00000587 54  55   .dynstr             ascii   Java_org_owasp_mastestapp_MastgTest_getTracerPidNative
12  0x000005e0 0x000005e0 67  68   .dynstr             ascii   Java_org_owasp_mastestapp_MastgTest_getTracerPidInlineSyscallNative
29  0x000008d0 0x000008d0 17  18   .rodata             ascii   /proc/self/status
30  0x000008e2 0x000008e2 10  11   .rodata             ascii   TracerPid:
31  0x000008ef 0x000008ef 13  14   .rodata             ascii   TracerPid:\t%d
282 0x000038dd 0x000008c0 67  68   .debug_str          ascii   Java_org_owasp_mastestapp_MastgTest_getTracerPidInlineSyscallNative
292 0x0000396a 0x0000094d 54  55   .debug_str          ascii   Java_org_owasp_mastestapp_MastgTest_getTracerPidNative
414 0x00005513 0x0000029b 54  55   .strtab             ascii   Java_org_owasp_mastestapp_MastgTest_getTracerPidNative
420 0x0000556c 0x000002f4 67  68   .strtab             ascii   Java_org_owasp_mastestapp_MastgTest_getTracerPidInlineSyscallNative

Imported ptrace symbol:
16  0x000021c0 GLOBAL FUNC     ptrace

Cross-references to ptrace:
dbg.Java_org_owasp_mastestapp_MastgTest_ptraceSelfDetectNative 0x1fc4 [CALL:--x] bl sym.imp.ptrace

Disassembly around ptrace references:
       ┌─< 0x00001f84      tbz w8, 0x1f, 0x1f98                       ; tracerpid_ptrace_self.cpp:15:9    if (child_pid < 0)
      ┌──< 0x00001f88      b 0x1f8c
      ││   ; CODE XREF from dbg.Java_org_owasp_mastestapp_MastgTest_ptraceSelfDetectNative @ 0x1f88(x)
      └──> 0x00001f8c      mov w8, 2                                  ; tracerpid_ptrace_self.cpp:17:9        return 2;
          0x00001f90      stur w8, [x29, -4]
      ┌──< 0x00001f94      b 0x2094
      ││   ; CODE XREF from dbg.Java_org_owasp_mastestapp_MastgTest_ptraceSelfDetectNative @ 0x1f84(x)
      │└─> 0x00001f98      ldr w8, [var_14h]                          ; tracerpid_ptrace_self.cpp:20:9    if (child_pid == 0)
      │┌─< 0x00001f9c      cbnz w8, 0x1ffc
     ┌───< 0x00001fa0      b 0x1fa4
     │││   ; CODE XREF from dbg.Java_org_owasp_mastestapp_MastgTest_ptraceSelfDetectNative @ 0x1fa0(x)
     └───> 0x00001fa4      bl sym.imp.getppid                         ; tracerpid_ptrace_self.cpp:22:34        const pid_t parent_pid = getppid();
      ││   0x00001fa8      str w0, [arg_40hx10]                       ; tracerpid_ptrace_self.cpp:22:21        const pid_t parent_pid = getppid();
      ││   0x00001fac      bl sym.imp.__errno                         ; tracerpid_ptrace_self.cpp:24:9        errno = 0;
      ││   0x00001fb0      str wzr, [x0]                              ; tracerpid_ptrace_self.cpp:24:15        errno = 0;
      ││   0x00001fb4      ldr w1, [arg_40hx10]                       ; tracerpid_ptrace_self.cpp:25:34        if (ptrace(PTRACE_SEIZE, parent_pid, nullptr, nullptr) == 0)
      ││   0x00001fb8      mov w0, 0x4206                             ; tracerpid_ptrace_self.cpp:25:13        if (ptrace(PTRACE_SEIZE, parent_pid, nullptr, nullptr) == 0)
      ││   0x00001fbc      mov x3, xzr                                ; void*data
      ││   0x00001fc0      mov x2, x3                                 ; void*addr
           0x00001fc4      bl sym.imp.ptrace                          ; long ptrace(__ptrace_request request, pid_t pid, void*addr, void*data)
       ┌─< 0x00001fc8      cbnz x0, 0x1fd8                            ; tracerpid_ptrace_self.cpp:25:13        if (ptrace(PTRACE_SEIZE, parent_pid, nullptr, nullptr) == 0)
      ┌──< 0x00001fcc      b 0x1fd0
      ││   ; CODE XREF from dbg.Java_org_owasp_mastestapp_MastgTest_ptraceSelfDetectNative @ 0x1fcc(x)
      └──> 0x00001fd0      mov w0, wzr                                ; tracerpid_ptrace_self.cpp:27:13            _exit(0);
          0x00001fd4      bl sym.imp._exit                           ; void exit(int status)
          ; CODE XREF from dbg.Java_org_owasp_mastestapp_MastgTest_ptraceSelfDetectNative @ 0x1fc8(x)
       └─> 0x00001fd8      bl sym.imp.__errno                         ; tracerpid_ptrace_self.cpp:30:13        if (errno == EPERM)
           0x00001fdc      ldr w8, [x0]
           0x00001fe0      subs w8, w8, 1
       ┌─< 0x00001fe4      b.ne 0x1ff4
      ┌──< 0x00001fe8      b 0x1fec
      ││   ; CODE XREF from dbg.Java_org_owasp_mastestapp_MastgTest_ptraceSelfDetectNative @ 0x1fe8(x)
      └──> 0x00001fec      mov w0, 1                                  ; tracerpid_ptrace_self.cpp:32:13            _exit(1);
          0x00001ff0      bl sym.imp._exit                           ; void exit(int status)
          ; CODE XREF from dbg.Java_org_owasp_mastestapp_MastgTest_ptraceSelfDetectNative @ 0x1fe4(x)
       └─> 0x00001ff4      mov w0, 2                                  ; tracerpid_ptrace_self.cpp:35:9        _exit(2);
           0x00001ff8      bl sym.imp._exit                           ; void exit(int status)
           ; CODE XREF from dbg.Java_org_owasp_mastestapp_MastgTest_ptraceSelfDetectNative @ 0x1f9c(x)
           0x00001ffc      add x1, sp, 0xc                            ; tracerpid_ptrace_self.cpp:0:9
           0x00002000      mov w2, wzr

Evaluation

The test passes because the app implements native debugger detection checks, for example getTracerPidFromProcStatus() at MastgTest_reversed.java line 56, native TracerPid JNI checks invoked at lines 79 and 91, and /proc/self/status parsing at lines 126 and 133. The output also reports native ptrace anti-debugging logic in the compiled library: mov w0, 0x4206 loads the PTRACE_SEIZE request before calling sym.imp.ptrace.

Note that this demo uses ptrace(PTRACE_SEIZE) for its self-check, but you should also check for ptrace(PTRACE_ATTACH), which is an alternative approach used in real-world apps.