├── .gitignore
├── .idea
├── .gitignore
├── .name
├── compiler.xml
├── gradle.xml
└── misc.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── id
│ │ └── kuro
│ │ └── androidnativeguard
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── cpp
│ │ ├── CMakeLists.txt
│ │ ├── Modules
│ │ │ ├── AntiDebug
│ │ │ │ ├── AntiDebug.cpp
│ │ │ │ └── AntiDebug.h
│ │ │ ├── AntiDump
│ │ │ │ ├── AntiDump.cpp
│ │ │ │ └── AntiDump.h
│ │ │ ├── AntiLibPatch
│ │ │ │ ├── AntiLibPatch.cpp
│ │ │ │ └── AntiLibPatch.h
│ │ │ ├── FridaDetect
│ │ │ │ ├── FridaDetect.cpp
│ │ │ │ └── FridaDetect.h
│ │ │ ├── IModule.h
│ │ │ ├── RiGisk
│ │ │ │ ├── RiGisk.cpp
│ │ │ │ └── RiGisk.h
│ │ │ └── RootDetect
│ │ │ │ ├── RootDetect.cpp
│ │ │ │ └── RootDetect.h
│ │ ├── SecureAPI
│ │ │ ├── SecureAPI.h
│ │ │ └── syscall
│ │ │ │ ├── arm64-v8a
│ │ │ │ └── syscall_arch.h
│ │ │ │ ├── armeabi-v7a
│ │ │ │ └── syscall_arch.h
│ │ │ │ ├── x86
│ │ │ │ └── syscall_arch.h
│ │ │ │ └── x86_64
│ │ │ │ └── syscall_arch.h
│ │ ├── Utils
│ │ │ ├── ElfImg.cpp
│ │ │ ├── ElfImg.h
│ │ │ ├── Log.h
│ │ │ └── obfuscate.h
│ │ ├── constants.h
│ │ └── main.cpp
│ ├── java
│ │ └── id
│ │ │ └── kuro
│ │ │ └── androidnativeguard
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── border_background.xml
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-anydpi-v33
│ │ └── ic_launcher.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values-night
│ │ └── themes.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── java
│ └── id
│ └── kuro
│ └── androidnativeguard
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | /.idea/*
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 | .cxx
11 | local.properties
12 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | Android Native Guard
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # What is it?
2 | Android Native Guard is a proof of concepts on how to detect various threat that might harm your application's integrity.
3 |
4 | # How secure is it?
5 | Android Native Guard used what's called `Secure API` and low-level function call through inline `syscall` to prevent any bypass that could potentially break the security system.
6 | Android Native Guard used various open-source projects to implement a true-secure android application system.
7 |
8 | # Modules
9 | - Debugger Detection
10 | - Frida Detection
11 | - Riru & Zygisk Detection
12 | - Root Detection
13 | - Memory Access & Dump Detection
14 | - Library Patch & Hook Detection
15 |
16 | # TODO
17 | - Better documentation (codes & README)
18 | - App Tamper Detection (signature, checksum, etc)
19 | - Magisk-Hide Detection Module (e.g. Shamiko)
20 | - Blacklist for AntiLibPatch module
21 |
22 | # Notes
23 | - Don't forget to add `android:extractNativeLibs="true"` to your `AndroidManifest.xml` so that module _AntiLibPatch_ can work properly.
24 |
25 | # Credits
26 | https://github.com/darvincisec/AntiDebugandMemoryDump
27 |
28 | https://github.com/darvincisec/DetectFrida
29 |
30 | https://github.com/aimardcr/NativeDetector
31 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | }
4 |
5 | android {
6 | namespace 'id.kuro.androidnativeguard'
7 | compileSdk 33
8 |
9 | defaultConfig {
10 | applicationId "id.kuro.androidnativeguard"
11 | minSdk 24
12 | targetSdk 35
13 | versionCode 1
14 | versionName "1.0"
15 | multiDexEnabled true
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | externalNativeBuild {
19 | cmake {
20 |
21 | }
22 | }
23 | }
24 |
25 | buildTypes {
26 | release {
27 | minifyEnabled false
28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
29 | }
30 | }
31 | compileOptions {
32 | sourceCompatibility JavaVersion.VERSION_1_8
33 | targetCompatibility JavaVersion.VERSION_1_8
34 | }
35 | externalNativeBuild {
36 | cmake {
37 | path file('src/main/cpp/CMakeLists.txt')
38 | version '3.22.1'
39 | }
40 | }
41 | }
42 |
43 | dependencies {
44 | implementation 'com.android.support:multidex:1.0.0'
45 | implementation 'androidx.appcompat:appcompat:1.6.1'
46 | implementation 'com.google.android.material:material:1.8.0'
47 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
48 | testImplementation 'junit:junit:4.13.2'
49 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
50 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
51 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/id/kuro/androidnativeguard/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package id.kuro.androidnativeguard;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 | assertEquals("id.kuro.androidnativeguard", appContext.getPackageName());
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/cpp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.22.1)
2 |
3 | project(NativeGuard)
4 |
5 | set(CMAKE_CXX_STANDARD 20)
6 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
7 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-register")
8 |
9 | if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
10 | # Set visibility to hidden by default
11 | set(BUILD_CXX_AND_C_FLAGS "-fvisibility=hidden -fvisibility-inlines-hidden \
12 | -fno-rtti -fno-exceptions")
13 |
14 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_RELEASE} ${BUILD_CXX_AND_C_FLAGS}")
15 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_RELEASE} ${BUILD_CXX_AND_C_FLAGS}")
16 |
17 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdata-sections -ffunction-sections")
18 | else ()
19 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g -DDEBUG")
20 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -DDEBUG")
21 |
22 | add_compile_definitions(DEBUG_BUILD)
23 | endif ()
24 |
25 | if (${ANDROID_ABI} STREQUAL "armeabi-v7a")
26 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -marm")
27 | endif ()
28 |
29 | # Add root directory reference for easier header includes,
30 | # It also let you avoid relative path e.g. #include "../include/header.h"
31 | include_directories(./)
32 |
33 | add_library(NativeGuard SHARED
34 | main.cpp
35 | Utils/ElfImg.cpp
36 | Modules/AntiDebug/AntiDebug.cpp
37 | Modules/FridaDetect/FridaDetect.cpp
38 | Modules/RiGisk/RiGisk.cpp
39 | Modules/RootDetect/RootDetect.cpp
40 | Modules/AntiDump/AntiDump.cpp
41 | Modules/AntiLibPatch/AntiLibPatch.cpp)
42 |
43 | target_include_directories(NativeGuard PRIVATE
44 | ${CMAKE_CURRENT_SOURCE_DIR}/SecureAPI
45 | ${CMAKE_CURRENT_SOURCE_DIR}/SecureAPI/syscall/${ANDROID_ABI}
46 | ${CMAKE_CURRENT_SOURCE_DIR}/Utils
47 | ${CMAKE_CURRENT_SOURCE_DIR}/Modules)
48 |
49 | target_link_libraries(NativeGuard log)
--------------------------------------------------------------------------------
/app/src/main/cpp/Modules/AntiDebug/AntiDebug.cpp:
--------------------------------------------------------------------------------
1 | #include "AntiDebug.h"
2 | #include "SecureAPI.h"
3 | #include "Log.h"
4 | #include "obfuscate.h"
5 |
6 | #include
7 | #include
8 |
9 | AntiDebug::AntiDebug(void (*callback)()) : onDebuggerDetected(callback) {
10 |
11 | }
12 |
13 | const char *AntiDebug::getName() {
14 | return AY_OBFUSCATE("Debugger Detection");
15 | }
16 |
17 | eSeverity AntiDebug::getSeverity() {
18 | return HIGH;
19 | }
20 |
21 | bool AntiDebug::execute() {
22 | LOGI("AntiDebug::execute");
23 | if (scanStatus()) {
24 | return true;
25 | }
26 | if (scanTaskStatuses()) {
27 | return true;
28 | }
29 | LOGI("AntiDebug::execute false");
30 | return false;
31 | }
32 |
33 | bool AntiDebug::scanStatus() {
34 | LOGI("AntiDebug::scanStatus");
35 | int fd = SecureAPI::openat(AT_FDCWD, AY_OBFUSCATE("/proc/self/status"), O_RDONLY, 0);
36 | if (fd == -1) {
37 | return true;
38 | }
39 | LOGI("AntiDebug::scanStatus fd: %d", fd);
40 |
41 | if (checkTracerPid(fd)) {
42 | SecureAPI::close(fd);
43 | return true;
44 | }
45 |
46 | SecureAPI::close(fd);
47 | return false;
48 | }
49 |
50 | bool AntiDebug::scanTaskStatuses() {
51 | LOGI("AntiDebug::scanTaskStatuses");
52 | int fd = SecureAPI::openat(AT_FDCWD, AY_OBFUSCATE("/proc/self/task"), O_RDONLY | O_DIRECTORY, 0);
53 | if (fd == -1) {
54 | return true;
55 | }
56 |
57 | struct linux_dirent64 *dirp;
58 | char buf[512];
59 | int nread;
60 |
61 | while ((nread = SecureAPI::getdents64(fd, (struct linux_dirent64 *) buf, sizeof(buf))) > 0) {
62 | for (int bpos = 0; bpos < nread;) {
63 | dirp = (struct linux_dirent64 *) (buf + bpos);
64 | if (dirp->d_type == DT_DIR) {
65 | LOGI("AntiDebug::scanTaskStatuses dirp->d_name: %s", dirp->d_name);
66 | if (!SecureAPI::strcmp(dirp->d_name, AY_OBFUSCATE(".")) || !SecureAPI::strcmp(dirp->d_name, AY_OBFUSCATE(".."))) {
67 | bpos += dirp->d_reclen;
68 | continue;
69 | }
70 |
71 | char statusPath[512];
72 | sprintf(statusPath, AY_OBFUSCATE("/proc/self/task/%s/status"), dirp->d_name);
73 | int statusFd = SecureAPI::openat(AT_FDCWD, statusPath, O_RDONLY, 0);
74 | LOGI("AntiDebug::scanTaskStatuses statusPath: %s | statusFd: %d", statusPath, statusFd);
75 | if (statusFd == -1) {
76 | bpos += dirp->d_reclen;
77 | continue;
78 | }
79 |
80 | if (checkTracerPid(statusFd)) {
81 | SecureAPI::close(statusFd);
82 | SecureAPI::close(fd);
83 | return true;
84 | }
85 |
86 | SecureAPI::close(statusFd);
87 | }
88 | bpos += dirp->d_reclen;
89 | }
90 | }
91 |
92 | SecureAPI::close(fd);
93 | return false;
94 | }
95 |
96 | bool AntiDebug::checkTracerPid(int fd) {
97 | char buf[512];
98 | while (readLine(fd, buf, sizeof(buf)) > 0) {
99 | if (SecureAPI::strncmp(buf, AY_OBFUSCATE("TracerPid:"), 10) == 0) {
100 | int pid = atoi(buf + 10);
101 | LOGI("AntiDebug::checkTracerPid(%d) pid: %d", fd, pid);
102 | if (pid != 0) {
103 | if (this->onDebuggerDetected) {
104 | time_t now = time(0);
105 | if (std::find(this->m_debug_times.begin(), this->m_debug_times.end(), now) == this->m_debug_times.end()) {
106 | this->m_debug_times.push_back(now);
107 | this->onDebuggerDetected();
108 | }
109 | }
110 | return true;
111 | }
112 | }
113 | }
114 | return false;
115 | }
116 |
117 | size_t AntiDebug::readLine(int fd, char *buf, size_t bufSize) {
118 | size_t i = 0, n;
119 | char c;
120 | while (i < bufSize - 1) {
121 | n = SecureAPI::read(fd, &c, 1);
122 | if (n == -1) {
123 | return -1;
124 | }
125 |
126 | if (n == 0 || c == '\n') {
127 | break;
128 | }
129 |
130 | buf[i++] = c;
131 | }
132 | buf[i] = '\0';
133 |
134 | return i;
135 | }
--------------------------------------------------------------------------------
/app/src/main/cpp/Modules/AntiDebug/AntiDebug.h:
--------------------------------------------------------------------------------
1 | #include "../IModule.h"
2 |
3 | class AntiDebug : public IModule {
4 | public:
5 | AntiDebug(void (*)());
6 | const char *getName() override;
7 | eSeverity getSeverity() override;
8 |
9 | bool execute() override;
10 | private:
11 | bool scanStatus();
12 | bool scanTaskStatuses();
13 |
14 | bool checkTracerPid(int fd);
15 | size_t readLine(int fd, char *buf, size_t bufSize);
16 |
17 | std::vector m_debug_times;
18 | void (*onDebuggerDetected)();
19 | };
--------------------------------------------------------------------------------
/app/src/main/cpp/Modules/AntiDump/AntiDump.cpp:
--------------------------------------------------------------------------------
1 | #include "AntiDump.h"
2 | #include "SecureAPI.h"
3 | #include "Log.h"
4 | #include "obfuscate.h"
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | AntiDump::AntiDump(void (*callback)()) : onDumpDetected(callback) {
13 | this->m_fd = SecureAPI::inotify_init1(0);
14 | if (this->m_fd == -1) {
15 | LOGI("AntiDump::execute inotify_init1 failed");
16 | if (errno == EMFILE || errno == ENFILE) {
17 | LOGI("AntiDump::execute inotify_init1 probably failed because of max_user_watches being tampered.");
18 | }
19 | return;
20 | }
21 |
22 | this->m_wd[this->m_count++] = SecureAPI::inotify_add_watch(this->m_fd, AY_OBFUSCATE("/proc/self/maps"), IN_ACCESS | IN_OPEN);
23 | this->m_wd[this->m_count++] = SecureAPI::inotify_add_watch(this->m_fd, AY_OBFUSCATE("/proc/self/mem"), IN_ACCESS | IN_OPEN);
24 | this->m_wd[this->m_count++] = SecureAPI::inotify_add_watch(this->m_fd, AY_OBFUSCATE("/proc/self/pagemap"), IN_ACCESS | IN_OPEN);
25 |
26 | struct linux_dirent64 *dirp;
27 | char buf[512];
28 | int nread;
29 |
30 | int task = SecureAPI::openat(AT_FDCWD, AY_OBFUSCATE("/proc/self/task"), O_RDONLY | O_DIRECTORY, 0);
31 | while ((nread = SecureAPI::getdents64(task, (struct linux_dirent64 *) buf, sizeof(buf))) > 0) {
32 | for (int bpos = 0; bpos < nread;) {
33 | dirp = (struct linux_dirent64 *) (buf + bpos);
34 | if (!SecureAPI::strcmp(dirp->d_name, AY_OBFUSCATE(".")) ||
35 | !SecureAPI::strcmp(dirp->d_name, AY_OBFUSCATE(".."))) {
36 | bpos += dirp->d_reclen;
37 | continue;
38 | }
39 | if (dirp->d_type == DT_DIR) {
40 | char memPath[512], pagemapPath[512];
41 | sprintf(memPath, AY_OBFUSCATE("/proc/self/task/%s/mem"), dirp->d_name);
42 | sprintf(pagemapPath, AY_OBFUSCATE("/proc/self/task/%s/pagemap"), dirp->d_name);
43 |
44 | this->m_wd[this->m_count++] = SecureAPI::inotify_add_watch(this->m_fd, memPath, IN_ACCESS | IN_OPEN);
45 | this->m_wd[this->m_count++] = SecureAPI::inotify_add_watch(this->m_fd, pagemapPath, IN_ACCESS | IN_OPEN);
46 | }
47 | bpos += dirp->d_reclen;
48 | }
49 | }
50 | SecureAPI::close(task);
51 | }
52 |
53 | const char *AntiDump::getName() {
54 | return AY_OBFUSCATE("Memory Dump Detection");
55 | }
56 |
57 | eSeverity AntiDump::getSeverity() {
58 | return MEDIUM;
59 | }
60 |
61 | bool AntiDump::execute() {
62 | if (this->m_fd == -1) {
63 | return false;
64 | }
65 |
66 | char buf[4096];
67 | int len = (int) SecureAPI::read(this->m_fd, buf, sizeof(buf));
68 | if (len > 0) {
69 | LOGI("AntiDump::execute len: %d", len);
70 | struct inotify_event *event;
71 | for (char *ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + event->len) {
72 | event = (struct inotify_event *) ptr;
73 | if (event->mask & IN_ACCESS || event->mask & IN_OPEN) {
74 | LOGI("AntiDump::execute event->mask: %d", event->mask);
75 | SecureAPI::close(this->m_fd);
76 |
77 | if (this->onDumpDetected) {
78 | time_t now = time(0);
79 | if (std::find(this->m_dump_times.begin(), this->m_dump_times.end(), now) == this->m_dump_times.end()) {
80 | this->m_dump_times.push_back(now);
81 | this->onDumpDetected();
82 | }
83 | }
84 | return true;
85 | }
86 | }
87 | }
88 | return false;
89 | }
--------------------------------------------------------------------------------
/app/src/main/cpp/Modules/AntiDump/AntiDump.h:
--------------------------------------------------------------------------------
1 | #include "../IModule.h"
2 |
3 | class AntiDump : public IModule {
4 | public:
5 | AntiDump(void (*)() = 0);
6 | const char *getName() override;
7 | eSeverity getSeverity() override;
8 |
9 | bool execute() override;
10 | private:
11 | int m_fd = -1;
12 | int m_wd[100];
13 | int m_count = 0;
14 |
15 | std::vector m_dump_times;
16 | void (*onDumpDetected)();
17 | };
--------------------------------------------------------------------------------
/app/src/main/cpp/Modules/AntiLibPatch/AntiLibPatch.cpp:
--------------------------------------------------------------------------------
1 | #include "AntiLibPatch.h"
2 | #include "SecureAPI.h"
3 | #include "Log.h"
4 | #include "obfuscate.h"
5 | #include "constants.h"
6 | #include "ElfImg.h"
7 |
8 | #include
9 | #include