|
1 | | -/* |
2 | | - * This file is part of LSPosed. |
3 | | - * |
4 | | - * LSPosed is free software: you can redistribute it and/or modify |
5 | | - * it under the terms of the GNU General Public License as published by |
6 | | - * the Free Software Foundation, either version 3 of the License, or |
7 | | - * (at your option) any later version. |
8 | | - * |
9 | | - * LSPosed is distributed in the hope that it will be useful, |
10 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | - * GNU General Public License for more details. |
13 | | - * |
14 | | - * You should have received a copy of the GNU General Public License |
15 | | - * along with LSPosed. If not, see <https://www.gnu.org/licenses/>. |
16 | | - * |
17 | | - * Copyright (C) 2022 LSPosed Contributors |
18 | | - */ |
19 | | - |
20 | | -// |
21 | | -// Created by Nullptr on 2022/4/1. |
22 | | -// |
23 | | - |
24 | | -#include <stdio.h> |
25 | | -#include <stdlib.h> |
26 | | -#include <string.h> |
27 | 1 | #include <sys/socket.h> |
28 | 2 | #include <sys/un.h> |
29 | 3 | #include <unistd.h> |
30 | 4 |
|
| 5 | +#include <cstdio> |
| 6 | +#include <cstdlib> |
| 7 | +#include <cstring> |
| 8 | +#include <string> |
| 9 | +#include <vector> |
| 10 | + |
31 | 11 | #include "logging.h" |
32 | 12 |
|
| 13 | +// Access to the process environment variables |
| 14 | +extern "C" char **environ; |
| 15 | + |
33 | 16 | #if defined(__LP64__) |
34 | 17 | #define LP_SELECT(lp32, lp64) lp64 |
35 | 18 | #else |
36 | 19 | #define LP_SELECT(lp32, lp64) lp32 |
37 | 20 | #endif |
38 | 21 |
|
39 | | -#define ID_VEC(is64, is_debug) (((is64) << 1) | (is_debug)) |
| 22 | +namespace { |
40 | 23 |
|
41 | | -const char kSockName[] = "5291374ceda0aef7c5d86cd2a4f6a3ac\0"; |
| 24 | +constexpr char kSockName[] = "5291374ceda0aef7c5d86cd2a4f6a3ac"; |
42 | 25 |
|
43 | | -static ssize_t xrecvmsg(int sockfd, struct msghdr *msg, int flags) { |
44 | | - int rec = recvmsg(sockfd, msg, flags); |
| 26 | +/** |
| 27 | + * Calculates a vector ID based on architecture and debug status. |
| 28 | + */ |
| 29 | +inline int get_id_vec(bool is64, bool is_debug) { |
| 30 | + return (static_cast<int>(is64) << 1) | static_cast<int>(is_debug); |
| 31 | +} |
| 32 | + |
| 33 | +/** |
| 34 | + * Wraps recvmsg with error logging. |
| 35 | + */ |
| 36 | +ssize_t xrecvmsg(int sockfd, struct msghdr *msg, int flags) { |
| 37 | + ssize_t rec = recvmsg(sockfd, msg, flags); |
45 | 38 | if (rec < 0) { |
46 | 39 | PLOGE("recvmsg"); |
47 | 40 | } |
48 | 41 | return rec; |
49 | 42 | } |
50 | 43 |
|
51 | | -static void *recv_fds(int sockfd, char *cmsgbuf, size_t bufsz, int cnt) { |
| 44 | +/** |
| 45 | + * Receives file descriptors passed over a Unix domain socket using SCM_RIGHTS. |
| 46 | + * |
| 47 | + * @return Pointer to the FD data on success, nullptr on failure. |
| 48 | + */ |
| 49 | +void *recv_fds(int sockfd, char *cmsgbuf, size_t bufsz, int cnt) { |
52 | 50 | struct iovec iov = { |
53 | 51 | .iov_base = &cnt, |
54 | 52 | .iov_len = sizeof(cnt), |
55 | 53 | }; |
56 | | - struct msghdr msg = { |
57 | | - .msg_iov = &iov, .msg_iovlen = 1, .msg_control = cmsgbuf, .msg_controllen = bufsz}; |
| 54 | + struct msghdr msg = {.msg_name = nullptr, |
| 55 | + .msg_namelen = 0, |
| 56 | + .msg_iov = &iov, |
| 57 | + .msg_iovlen = 1, |
| 58 | + .msg_control = cmsgbuf, |
| 59 | + .msg_controllen = bufsz, |
| 60 | + .msg_flags = 0}; |
| 61 | + |
| 62 | + if (xrecvmsg(sockfd, &msg, MSG_WAITALL) < 0) return nullptr; |
58 | 63 |
|
59 | | - xrecvmsg(sockfd, &msg, MSG_WAITALL); |
60 | 64 | struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); |
61 | 65 |
|
62 | | - if (msg.msg_controllen != bufsz || cmsg == NULL || |
| 66 | + if (msg.msg_controllen != bufsz || cmsg == nullptr || |
63 | 67 | cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) || cmsg->cmsg_level != SOL_SOCKET || |
64 | 68 | cmsg->cmsg_type != SCM_RIGHTS) { |
65 | | - return NULL; |
| 69 | + return nullptr; |
66 | 70 | } |
67 | 71 |
|
68 | 72 | return CMSG_DATA(cmsg); |
69 | 73 | } |
70 | 74 |
|
71 | | -static int recv_fd(int sockfd) { |
| 75 | +/** |
| 76 | + * Helper to receive a single FD from the socket. |
| 77 | + */ |
| 78 | +int recv_fd(int sockfd) { |
72 | 79 | char cmsgbuf[CMSG_SPACE(sizeof(int))]; |
73 | | - |
74 | 80 | void *data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1); |
75 | | - if (data == NULL) return -1; |
| 81 | + if (data == nullptr) return -1; |
76 | 82 |
|
77 | 83 | int result; |
78 | | - memcpy(&result, data, sizeof(int)); |
| 84 | + std::memcpy(&result, data, sizeof(int)); |
79 | 85 | return result; |
80 | 86 | } |
81 | 87 |
|
82 | | -static int read_int(int fd) { |
| 88 | +/** |
| 89 | + * Reads an integer acknowledgment from the socket. |
| 90 | + */ |
| 91 | +int read_int(int fd) { |
83 | 92 | int val; |
84 | 93 | if (read(fd, &val, sizeof(val)) != sizeof(val)) return -1; |
85 | 94 | return val; |
86 | 95 | } |
87 | 96 |
|
88 | | -static void write_int(int fd, int val) { |
| 97 | +/** |
| 98 | + * Writes an integer command/ID to the socket. |
| 99 | + */ |
| 100 | +void write_int(int fd, int val) { |
89 | 101 | if (fd < 0) return; |
90 | | - write(fd, &val, sizeof(val)); |
| 102 | + (void)write(fd, &val, sizeof(val)); |
91 | 103 | } |
92 | 104 |
|
| 105 | +} // namespace |
| 106 | + |
93 | 107 | int main(int argc, char **argv) { |
94 | 108 | LOGD("dex2oat wrapper ppid=%d", getppid()); |
| 109 | + |
| 110 | + // Prepare Unix domain socket address (Abstract Namespace) |
95 | 111 | struct sockaddr_un sock = {}; |
96 | 112 | sock.sun_family = AF_UNIX; |
97 | | - strlcpy(sock.sun_path + 1, kSockName, sizeof(sock.sun_path) - 1); |
| 113 | + // sock.sun_path[0] is already \0, so we copy name into sun_path + 1 |
| 114 | + std::strncpy(sock.sun_path + 1, kSockName, sizeof(sock.sun_path) - 2); |
| 115 | + |
| 116 | + // Abstract socket length: family + leading \0 + string length |
| 117 | + socklen_t len = sizeof(sock.sun_family) + strlen(kSockName) + 1; |
98 | 118 |
|
| 119 | + // 1. Get original dex2oat binary FD |
99 | 120 | int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); |
100 | | - size_t len = sizeof(sa_family_t) + strlen(sock.sun_path + 1) + 1; |
101 | | - if (connect(sock_fd, (struct sockaddr *)&sock, len)) { |
| 121 | + if (connect(sock_fd, reinterpret_cast<struct sockaddr *>(&sock), len)) { |
102 | 122 | PLOGE("failed to connect to %s", sock.sun_path + 1); |
103 | 123 | return 1; |
104 | 124 | } |
105 | | - write_int(sock_fd, ID_VEC(LP_SELECT(0, 1), strstr(argv[0], "dex2oatd") != NULL)); |
| 125 | + |
| 126 | + bool is_debug = (argv[0] != nullptr && std::strstr(argv[0], "dex2oatd") != nullptr); |
| 127 | + write_int(sock_fd, get_id_vec(LP_SELECT(false, true), is_debug)); |
| 128 | + |
106 | 129 | int stock_fd = recv_fd(sock_fd); |
107 | | - read_int(sock_fd); |
| 130 | + read_int(sock_fd); // Sync |
108 | 131 | close(sock_fd); |
109 | 132 |
|
| 133 | + // 2. Get liboat_hook.so FD |
110 | 134 | sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); |
111 | | - if (connect(sock_fd, (struct sockaddr *)&sock, len)) { |
| 135 | + if (connect(sock_fd, reinterpret_cast<struct sockaddr *>(&sock), len)) { |
112 | 136 | PLOGE("failed to connect to %s", sock.sun_path + 1); |
113 | 137 | return 1; |
114 | 138 | } |
| 139 | + |
115 | 140 | write_int(sock_fd, LP_SELECT(4, 5)); |
116 | 141 | int hooker_fd = recv_fd(sock_fd); |
117 | | - read_int(sock_fd); |
| 142 | + read_int(sock_fd); // Sync |
118 | 143 | close(sock_fd); |
119 | 144 |
|
120 | 145 | if (hooker_fd == -1) { |
121 | | - PLOGE("failed to read liboat_hook.so"); |
| 146 | + LOGE("failed to read liboat_hook.so"); |
| 147 | + } |
| 148 | + LOGD("sock: %s stock_fd: %d", sock.sun_path + 1, stock_fd); |
| 149 | + |
| 150 | + // Prepare arguments for execve |
| 151 | + // Logic: [linker] [/proc/self/fd/stock_fd] [original_args...] [--inline-max-code-units=0] |
| 152 | + std::vector<const char *> exec_argv; |
| 153 | + |
| 154 | + const char *linker_path = |
| 155 | + LP_SELECT("/apex/com.android.runtime/bin/linker", "/apex/com.android.runtime/bin/linker64"); |
| 156 | + |
| 157 | + char stock_fd_path[64]; |
| 158 | + std::snprintf(stock_fd_path, sizeof(stock_fd_path), "/proc/self/fd/%d", stock_fd); |
| 159 | + |
| 160 | + exec_argv.push_back(linker_path); |
| 161 | + exec_argv.push_back(stock_fd_path); |
| 162 | + |
| 163 | + // Append original arguments starting from argv[1] |
| 164 | + for (int i = 1; i < argc; ++i) { |
| 165 | + exec_argv.push_back(argv[i]); |
122 | 166 | } |
123 | | - LOGD("sock: %s %d", sock.sun_path + 1, stock_fd); |
124 | | - |
125 | | - const char *new_argv[argc + 2]; |
126 | | - for (int i = 0; i < argc; i++) new_argv[i] = argv[i]; |
127 | | - new_argv[argc] = "--inline-max-code-units=0"; |
128 | | - new_argv[argc + 1] = NULL; |
129 | | - |
130 | | - if (getenv("LD_LIBRARY_PATH") == NULL) { |
131 | | - char const *libenv = LP_SELECT( |
132 | | - "LD_LIBRARY_PATH=/apex/com.android.art/lib:/apex/com.android.os.statsd/lib", |
133 | | - "LD_LIBRARY_PATH=/apex/com.android.art/lib64:/apex/com.android.os.statsd/lib64"); |
134 | | - putenv((char *)libenv); |
| 167 | + |
| 168 | + // Append hooking flags to disable inline, which is our purpose of this wrapper, since we cannot |
| 169 | + // hook inlined target methods. |
| 170 | + exec_argv.push_back("--inline-max-code-units=0"); |
| 171 | + exec_argv.push_back(nullptr); |
| 172 | + |
| 173 | + // Setup Environment variables |
| 174 | + // Clear LD_LIBRARY_PATH to let the linker use internal config |
| 175 | + unsetenv("LD_LIBRARY_PATH"); |
| 176 | + |
| 177 | + // Set LD_PRELOAD to point to the hooker library FD |
| 178 | + std::string preload_val = "LD_PRELOAD=/proc/self/fd/" + std::to_string(hooker_fd); |
| 179 | + setenv("LD_PRELOAD", ("/proc/self/fd/" + std::to_string(hooker_fd)).c_str(), 1); |
| 180 | + |
| 181 | + // Pass original argv[0] as DEX2OAT_CMD |
| 182 | + if (argv[0]) { |
| 183 | + setenv("DEX2OAT_CMD", argv[0], 1); |
| 184 | + LOGD("DEX2OAT_CMD set to %s", argv[0]); |
135 | 185 | } |
136 | 186 |
|
137 | | - // Set LD_PRELOAD to load liboat_hook.so |
138 | | - const int STRING_BUFFER = 50; |
139 | | - char env_str[STRING_BUFFER]; |
140 | | - snprintf(env_str, STRING_BUFFER, "LD_PRELOAD=/proc/%d/fd/%d", getpid(), hooker_fd); |
141 | | - putenv(env_str); |
142 | | - LOGD("Set env %s", env_str); |
| 187 | + LOGI("Executing via linker: %s executing %s", linker_path, stock_fd_path); |
143 | 188 |
|
144 | | - fexecve(stock_fd, (char **)new_argv, environ); |
| 189 | + // Perform the execution |
| 190 | + execve(linker_path, const_cast<char *const *>(exec_argv.data()), environ); |
145 | 191 |
|
146 | | - PLOGE("fexecve failed"); |
| 192 | + // If we reach here, execve failed |
| 193 | + PLOGE("execve failed"); |
147 | 194 | return 2; |
148 | 195 | } |
0 commit comments