Skip to content

Commit aff6c21

Browse files
committed
Add detection using smaps
Currently, detection using stats map is still unstable, we add this commit for future tests.
1 parent d9a1119 commit aff6c21

File tree

5 files changed

+143
-9
lines changed

5 files changed

+143
-9
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Detecting library injection in memory using `solist` and `virtual maps`
1+
# Detecting library injection in memory
22

33
## Detection using `solist`
44

app/src/main/cpp/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ set(CMAKE_CXX_STANDARD 20)
2929
# used in the AndroidManifest.xml file.
3030
add_library(${CMAKE_PROJECT_NAME} SHARED
3131
# List C/C++ source files with relative paths to this CMakeLists.txt.
32-
elf_util.cpp native-lib.cpp solist.cpp vmap.cpp)
32+
elf_util.cpp native-lib.cpp smap.cpp solist.cpp vmap.cpp)
3333

3434
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC include)
3535
# Specifies libraries CMake should link to your target library. You

app/src/main/cpp/include/smap.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <stdio.h>
2+
#include <string>
3+
#include <unistd.h>
4+
5+
namespace StatsMap {
6+
struct SmapsEntry {
7+
int64_t size_kb = -1;
8+
int64_t private_dirty_kb = -1;
9+
int64_t swap_kb = -1;
10+
std::string pathname;
11+
};
12+
struct SmapsParserState {
13+
bool parsed_header = false;
14+
SmapsEntry current_entry{};
15+
};
16+
17+
template <typename T> static bool ParseSmaps(FILE *f, T callback);
18+
static inline const char *FindNthToken(const char *line, size_t n, size_t size);
19+
20+
template <typename T>
21+
static bool ParseSmapsLine(char *line, size_t size, SmapsParserState *state,
22+
T callback);
23+
24+
SmapsEntry DetectInjection(std::string lib);
25+
} // namespace StatsMap

app/src/main/cpp/native-lib.cpp

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "logging.h"
2+
#include "smap.h"
23
#include "solist.hpp"
34
#include "vmap.hpp"
45
#include <format>
@@ -11,19 +12,33 @@ Java_org_matrix_demo_MainActivity_stringFromJNI(JNIEnv *env,
1112

1213
std::string solist_detection = "No injection found using solist";
1314
std::string vmap_detection = "No injection found using vitrual map";
15+
std::string smap_detection = "No injection found using stats map";
1416
SoList::SoInfo *abnormal_soinfo = SoList::DetectInjection();
15-
VirtualMap::MapInfo *abnormal_map = VirtualMap::DetectInjection();
17+
VirtualMap::MapInfo *abnormal_vmap = VirtualMap::DetectInjection();
18+
/* StatsMap::SmapsEntry abnormal_smap = */
19+
/* StatsMap::DetectInjection(std::string("libharfbuzz_ng.so")); */
20+
1621
if (abnormal_soinfo != nullptr) {
17-
solist_detection = std::format("Solist: injection at {}", (void *)abnormal_soinfo);
22+
solist_detection =
23+
std::format("Solist: injection at {}", (void *)abnormal_soinfo);
1824
LOGE("Abnormal soinfo %p: %s loaded at %s", abnormal_soinfo,
1925
abnormal_soinfo->get_name(), abnormal_soinfo->get_path());
2026
}
2127

22-
if (abnormal_map != nullptr) {
23-
vmap_detection = std::format("Virtual map: injection at {}", abnormal_map->path);
24-
LOGE("Abnormal map %s: [0x%lx-0x%lx]", abnormal_map->path.data(),
25-
abnormal_map->start, abnormal_map->end);
28+
if (abnormal_vmap != nullptr) {
29+
vmap_detection =
30+
std::format("Virtual map: injection at {}", abnormal_vmap->path);
31+
LOGE("Abnormal vmap %s: [0x%lx-0x%lx]", abnormal_vmap->path.data(),
32+
abnormal_vmap->start, abnormal_vmap->end);
2633
}
2734

28-
return env->NewStringUTF((solist_detection + "\n" + vmap_detection).c_str());
35+
/* if (abnormal_smap.private_dirty_kb != -1) { */
36+
/* smap_detection = */
37+
/* std::format("Stats map: injection at {}", abnormal_smap.pathname); */
38+
/* LOGE("Abnormal smap %s", abnormal_smap.pathname.data()); */
39+
/* } */
40+
41+
return env->NewStringUTF(
42+
(solist_detection + "\n" + vmap_detection + "\n" + smap_detection)
43+
.c_str());
2944
}

app/src/main/cpp/smap.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#include "smap.h"
2+
#include "logging.h"
3+
#include <errno.h>
4+
#include <inttypes.h>
5+
#include <stdio.h>
6+
#include <unistd.h>
7+
namespace StatsMap {
8+
9+
template <typename T> static bool ParseSmaps(FILE *f, T callback) {
10+
SmapsParserState state;
11+
size_t line_size = 1024;
12+
char *line = static_cast<char *>(malloc(line_size));
13+
for (;;) {
14+
errno = 0;
15+
ssize_t rd = getline(&line, &line_size, f);
16+
if (rd == -1) {
17+
free(line);
18+
if (state.parsed_header)
19+
callback(state.current_entry);
20+
return errno == 0;
21+
} else {
22+
if (line[rd - 1] == '\n') {
23+
line[rd - 1] = '\0';
24+
rd--;
25+
}
26+
if (!ParseSmapsLine(line, static_cast<size_t>(rd), &state, callback)) {
27+
free(line);
28+
return false;
29+
}
30+
}
31+
}
32+
}
33+
34+
static inline const char *FindNthToken(const char *line, size_t n,
35+
size_t size) {
36+
size_t tokens = 0;
37+
bool parsing_token = false;
38+
for (size_t i = 0; i < size; ++i) {
39+
if (!parsing_token && line[i] != ' ') {
40+
parsing_token = true;
41+
if (tokens++ == n)
42+
return line + static_cast<ssize_t>(i);
43+
}
44+
if (line[i] == ' ')
45+
parsing_token = false;
46+
}
47+
return nullptr;
48+
}
49+
50+
template <typename T>
51+
static bool ParseSmapsLine(char *line, size_t size, SmapsParserState *state,
52+
T callback) {
53+
char *first_token_end = static_cast<char *>(memchr(line, ' ', size));
54+
if (first_token_end == nullptr || first_token_end == line)
55+
return false; // Malformed.
56+
bool is_header = *(first_token_end - 1) != ':';
57+
if (is_header) {
58+
if (state->parsed_header)
59+
callback(state->current_entry);
60+
state->current_entry = {};
61+
const char *last_token_begin = FindNthToken(line, 5u, size);
62+
if (last_token_begin)
63+
state->current_entry.pathname.assign(last_token_begin);
64+
state->parsed_header = true;
65+
return true;
66+
}
67+
if (!state->parsed_header)
68+
return false;
69+
sscanf(line, "Size: %" PRId64 " kB", &state->current_entry.size_kb);
70+
sscanf(line, "Swap: %" PRId64 " kB", &state->current_entry.swap_kb);
71+
sscanf(line, "Private_Dirty: %" PRId64 " kB",
72+
&state->current_entry.private_dirty_kb);
73+
return true;
74+
}
75+
76+
SmapsEntry DetectInjection(std::string lib) {
77+
SmapsEntry injection;
78+
79+
auto self_maps = fopen("/proc/self/smaps", "re");
80+
ParseSmaps(self_maps, [&injection, lib](const SmapsEntry &entry) {
81+
if (entry.pathname.find(lib) != std::string::npos &&
82+
entry.private_dirty_kb > 0) {
83+
injection.pathname = entry.pathname;
84+
injection.private_dirty_kb = entry.private_dirty_kb;
85+
LOGD("Injection at %s: Private_Dirty %d kB", injection.pathname.data(),
86+
injection.private_dirty_kb);
87+
}
88+
});
89+
fclose(self_maps);
90+
91+
return injection;
92+
}
93+
94+
} // namespace StatsMap

0 commit comments

Comments
 (0)