Recently, the National Medical Products Administration announced the seizure of a batch of rabies vaccines produced by Changchun Changsheng Biotechnology Co., Ltd., which had falsified production records. Relevant illegal activities are under investigation. According to media reports, several vaccine companies, including Changsheng Biotechnology, Wuhan Biological Products, and Shenzhen Taikang, are all controlled by the same three individuals, and there have been multiple allegations of fraud. In response, JD.com founder Liu Qiangdong stated: 90% of the vaccines our children receive are from these companies; such people should at least be sentenced to life imprisonment without parole!
A new week has begun, and I am happy to meet you all again!
This article is contributed by an ordinary programmer, sharing methods to detect multiple instances of software, hoping to help everyone!
Ordinary Programmer ‘s blog address:
https://www.jianshu.com/u/38b409cda5af
Recently, there has been a business requirement for apps to perform local security checks such as detecting multiple instances of software, hook frameworks, and emulators to prevent cheating.
Anti-cheating has always been a common issue, and detecting multiple instances of software is often a crucial part of anti-cheating measures. During the research process, it was found that companies producing multiple instance software have targeted upgrades against anti-multiple instance measures, making it difficult to find a universal solution even with the latest information.
Therefore, building upon the work of predecessors, further research continues.
The reference solutions are derived from the following two posts:
“Android Multiple Instance/Clone Detection”
https://blog.darkness463.top/2018/05/04/Android-Virtual-Check/
“Android Virtual Machine Multiple Instance Detection”
https://www.jianshu.com/p/216d65d9971e
In summary, the solutions are:
1. Detection of private file paths;
2. Application list detection;
3. Maps detection;
4. PS detection;
The code will not be pasted here; the test results for these four solutions are as follows:
The test order is 1234, and the test results X indicate failure to detect, while O indicates successful detection of multiple instances;
The virtual app test version is the git open-source version; the commercial version has fixed the UID issue;
It can be seen that the detection effect is not ideal; none of the methods can universally detect the top-ranking multiple instance software on the market, and even on high-version devices, multiple instance software perfectly evaded detection.
To avoid ambiguity, the app mentioned here refers to the same software, defined as the normal running app called the body, and the app running on the multiple instance software called the clone. We introduce the following two concepts:
Narrow definition of multiple instances: as long as the app is opened through multiple instance software, it is considered multiple instances, even if only one app is running at the same time.
Broad definition of multiple instances: regardless of whether the app is running on multiple instance software, as long as during the running period, there are other ‘selves’ running, it is considered multiple instances.
(This is somewhat akin to the concept in “The Sixth Day,” where clones believe they are real people and discover identical counterparts, thinking the other is a clone.)
The four solutions we referenced earlier are aimed at detecting narrow definitions of multiple instances by judging characteristics when running on multiple instance software. Multiple instance software will also research these detection solutions and propose corresponding measures.
Therefore, we take a step back and think along the lines of detecting broad definitions of multiple instances. We allow the app to run on multiple instance software, but only one app (whether body or clone) can run on the same machine at the same time. As long as the app can detect that there is a same self running, it can eliminate the other or self-destruct, thereby achieving the goal of preventing broad definitions of multiple instances.
So how do we make these two apps meet?
WeChat accounts cannot be logged in simultaneously on different phones, relying on network requests to limit login devices.
So how do we handle this situation locally? Can we also complete the meeting through network communication?
The answer is certainly yes; otherwise, why would I write this article? By utilizing sockets, we can act as both client and server to meet our needs.
Acting as both server and client
1. After the app runs, it first acts as the sender, connecting to the local port at the appropriate time and sending an encrypted message. If there is a port connection and the encrypted message matches, it is considered that the app is already running (broad definition of multiple instances), and the receiving end will handle it;
2. The app then becomes the receiving end, waiting for possible incoming connections;
3. If any app starts (whether body or clone), it repeats steps 1 & 2 to achieve the goal of ‘only one app running at the same time’, solving the problem of broad definitions of multiple instances.
With the idea in place, the next step is implementation; the complete code can be found at the bottom of the article.
Step 1: Scan Local Ports
It is presumed that the netstat command is used to scan the local ports that are already open.
However, this method has three pitfalls:
1. netstat may not work on some machines (http://410063005.iteye.com/blog/1923543)
2. busybox may not work on some machines;
The OnePlus 5T does not have the busybox tool
3. The output of netstat is essentially pure printing from the source code; (https://blog.csdn.net/earbao/article/details/32191607)
Since there are these pitfalls, it is better to handle it manually. The essence of netstat is to read files like /proc/net/tcp and format the output, and the tcp file format is standardized. By studying the source code, we can find the relationships between ports. 0100007F:8CA7 actually refers to 127.0.0.1:36007
The /proc/net/tcp6 file
The key code for scanning the tcp file and formatting the ports is as follows:
String tcp6 = CommandUtil.getSingleInstance().exec("cat /proc/net/tcp6");
if (TextUtils.isEmpty(tcp6)) return;
String[] lines = tcp6.split("\n");
ArrayList<Integer> portList = new ArrayList<>();
for (int i = 0, len = lines.length; i < len; i++) {
int localHost = lines[i].indexOf("0100007F:");
//127.0.0.1:的位置
if (localHost < 0) continue;
String singlePort = lines[i].substring(localHost + 9, localHost + 13);
//截取端口
Integer port = Integer.parseInt(singlePort, 16);
//16进制转成10进制
portList.add(port);
}
Step 2: Initiate Connection Requests
Next, initiate a thread for each port to connect and send a custom message, which can just be the app’s package name (as multiple instance software often hooks the getPackageName method, we simply follow the multiple instance software’s lead).
try {
//Initiate connection and send message
Socket socket = new Socket("127.0.0.1", port);
socket.setSoTimeout(2000);
OutputStream outputStream = socket.getOutputStream();
outputStream.write((secret + "\n").getBytes("utf-8"));
outputStream.flush();
socket.shutdownOutput();
//Get input stream; no processing done here, purely printing
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String info = null;
while ((info = bufferedReader.readLine()) != null) {
Log.i(TAG, "ClientThread: " + info);
}
bufferedReader.close();
inputStream.close();
socket.close();
} catch (ConnectException e) {
Log.i(TAG, port + "port refused");
}
The process of actively connecting is completed; the app (which could be either body or clone) that started earlier receives the message and processes it.
Step 3: Become the Receiving End, Waiting for Connections
Next, become the receiving end, listening on a certain port, waiting for possible incoming app connections (which could be either body or clone).
private void startServer(String secret) {
Random random = new Random();
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("127.0.0.1",
random.nextInt(55534) + 10000));
//Open a port between 10000~65535
while (true) {
Socket socket = serverSocket.accept();
ReadThread readThread = new ReadThread(secret, socket);
//If many apps are using this solution, open a thread to handle each connection
readThread.start();
// serverSocket.close();
}
} catch (BindException e) {
startServer(secret);//may loop forever
} catch (IOException e) {
e.printStackTrace();
}
}
When opening a port, to avoid opening an already opened port, actively catch BindException and call recursively. This may lead to an infinite loop; however, in practice, it is not that common. The random port range of 10000~65535 usually only requires two attempts.
Each processing thread matches the encrypted message; if matched, it corresponds to a certain clone or body sending the encrypted message. Here, the receiving end actively runs a null pointer exception to kill itself. The handling method is somewhat like the dark forest rule in “The Three-Body Problem,” where whoever exposes themselves first dies.
private class ReadThread extends Thread {
private ReadThread(String secret, Socket socket) {
InputStream inputStream = null;
try {
inputStream = socket.getInputStream();
byte buffer[] = new byte[1024 * 4];
int temp = 0;
while ((temp = inputStream.read(buffer)) != -1) {
String result = new String(buffer, 0, temp);
if (result.contains(secret)) {
// System.exit(0);
// Process.killProcess(Process.myPid());
nullPointTV.setText("");
}
}
inputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
*Due to port communication requiring Internet permissions, this library will not upload any privacy over the network.
Tests conducted using the previously mentioned models and multiple instance software have shown that the detection effect is generally effective. Due to the wide variety of Android models, real device coverage testing is incomplete; feel free to raise issues on GitHub if you have the time.
It only needs to be called once in the application’s mainProcess. The emulator will grab localhost; the demo includes emulator detection.
This solution has been integrated into
EasyProtectorLib
https://jcenter.bintray.com/com/lahm/library/easy-protector-release/
GitHub address:
https://github.com/lamster2018/EasyProtector
See the Chinese documentation at:
https://www.jianshu.com/p/c37b1bdb4757
Usage method: VirtualApkCheckUtil.getSingleInstance().checkByPortListening(String secret);
Todo
1. Detection of multiple instances should provide callbacks for developers to handle independently;
2. Similarly, using the same idea, utilizing ContentProvider should also be feasible.
Welcome long press the image below -> scan the QR code in the image
Or scan the code to follow my public account