1 điểm bởi GN⁺ 2025-05-25 | 2 bình luận | Chia sẻ qua WhatsApp
  • tachy0n là một exploit leo thang đặc quyền kernel hoạt động trên iOS 13.0~13.5, được đưa vào unc0ver v5.0.0 như một 0day vào ngày 23/5/2020 và nhắm trực tiếp vào phiên bản iOS mới nhất khi đó
  • Cốt lõi là race condition Lightspeed của lio_listio, có thể giải phóng đối tượng kalloc.16 hai lần để khiến các đối tượng khác nhau cùng trỏ tới một vùng nhớ
  • Cùng họ lỗi này trước đó cũng từng được dùng trong jailbreak/untether Spice nhắm vào iOS 11, nhưng sandbox ứng dụng và môi trường racoon có những ràng buộc rất khác nhau về đặc quyền, sandbox và kỹ thuật spray
  • Exploit cho unc0ver lặp lại việc giành được chồng lấn OSData, rồi dùng IOBufferMemoryDescriptorIOAcceleratorFamily2 để dựng fake task và fake mach port
  • Từ iOS 14 trở đi, Apple chuyển sang hướng chặn chính chiến lược exploit bằng tách allocator, sequestering, PAC và hardening theo từng loại đối tượng; kiến thức công khai về exploit kernel iOS hiện đã tụt lại rất xa so với thời iOS 13

Vì sao việc công bố tachy0n là điều hiếm thấy

  • tachy0n là một exploit cũ ảnh hưởng từ iOS 13.0 đến 13.5, được công bố trong unc0ver v5.0.0 vào ngày 23/5/2020
  • Theo tiêu chuẩn thời điểm đó, đây là một kernel local privilege escalation (kernel LPE) khá điển hình, nhưng điểm hiếm thấy là nó được công khai như một 0day jailbreak ảnh hưởng tới phiên bản iOS mới nhất
  • Khoảng 1 tuần sau khi exploit được công bố, Apple phát hành bản vá dành riêng cho lỗi này
  • Lỗi này là 0day trên iOS 13.5, nhưng trước đó đã từng được khai thác dưới dạng 1day
  • Pwn20wnd đã tìm ra một 0day có thể tiếp cận từ sandbox ứng dụng, và điểm khởi đầu là kiểm thử hồi quy với các 1day đã biết
    • SockPuppet của iOS 12 được vá ở iOS 12.3 nhưng lại xuất hiện trở lại trên iOS 12.4
    • Trường hợp này cho thấy Apple thiếu kiểm thử hồi quy cho loại lỗi đó, và Pwn20wnd đã triển khai kiểm thử hồi quy cho một số 1day đã biết rồi nhận được hit

Lightspeed: race condition trong lio_listio

  • Lỗi cốt lõi của tachy0n là bug Lightspeed của Synacktiv, có liên quan tới CVE-2020-9859 và khả năng cả CVE-2018-4344
  • Lỗ hổng nằm trong syscall lio_listio, vốn thực hiện file I/O bất đồng bộ hoặc theo lô
  • Kernel cấp phát cấu trúc aio_lio_context để theo dõi các tác vụ I/O đã gửi
    • Cấu trúc này có các trường io_waiter, io_issued, io_completed
  • Công việc thực tế được xử lý ở luồng riêng, và khi toàn bộ I/O hoàn tất thì do_aio_completion sẽ giải phóng context đó
  • Nếu không có tác vụ nào được lập lịch, thì luồng hiện tại của lio_listio phải tự giải phóng context
  • Vấn đề là bước kiểm tra này có race condition
    • Tác vụ có thể đã được gửi sang luồng khác, nhưng cũng có thể hoàn tất trước thời điểm kiểm tra và khiến context bị giải phóng
    • Khi đó lio_listio lại tiếp tục kiểm tra lio_context đã trở thành dangling pointer

Cách double free dẫn tới exploit

  • Trình tự cần thiết cho exploit như sau
    • lio_listio cấp phát lio_context
    • Tác vụ hoàn tất và do_aio_completion giải phóng lio_context
    • Vùng nhớ đã giải phóng được cấp phát lại thành đối tượng do kẻ tấn công kiểm soát và bị làm cho trông như lio_context->io_issued == 0
    • lio_listio nhìn thấy điều đó và lại giải phóng đối tượng của kẻ tấn công
    • Cùng vùng nhớ đó tiếp tục được cấp phát lại cho một đối tượng khác, khiến hai allocation khác nhau cùng trỏ vào một vùng nhớ
  • Trên thiết bị 64-bit, lio_context nằm trong zone nhỏ nhất là kalloc.16
  • Trước iOS 14, các allocation site cùng dùng chung theo kích thước, bất kể loại đối tượng
    • Đối tượng C++, mảng con trỏ và buffer dữ liệu do người dùng cung cấp có thể tái sử dụng bộ nhớ của nhau nếu cùng bucket kích thước
  • Double free này vận hành khác với kiểu double free thông thường vốn dễ rơi ngay vào trạng thái chí mạng nếu không có tái cấp phát ở giữa
    • lio_context->io_issued không trở thành 0 trong lúc còn được cấp phát
    • Sau khi free, allocator ghi đè 8 byte đầu bằng giá trị canary cùng freelist pointer hoặc giá trị XOR của địa chỉ đối tượng
    • Vì vậy lần free thứ hai chỉ xảy ra nếu có tái cấp phát xen giữa và các byte 4~7 bằng 0
  • Exploit thực tế có thể thử lại race này nhiều lần, còn việc một đối tượng hệ thống khác tình cờ làm các byte cần thiết thành 0 để kích hoạt double free là khá hiếm trong thực chiến

Cách bug này từng được dùng trong Spice

  • Cùng bug đó cũng được dùng trong jailbreak/untether Spice nhắm tới iOS 11.x
  • Spice do nhóm Jake Blair thực hiện cùng với Sparkey và littlelailo, trong khi phiên bản mới nhất thời điểm đó đã là iOS 13.x
  • Mục tiêu là tạo ra mach port forgery cả trong môi trường ứng dụng lẫn môi trường racoon
    • Trong exploit kernel trước iOS 14, nếu có thể khiến một giá trị do người dùng cung cấp được diễn giải như con trỏ mach port thì các bước sau gần như trở nên dễ dàng một cách quyết định
  • Luồng cơ bản để tạo mach port forgery bằng Lightspeed như sau
    • Kích hoạt lần free đầu tiên của lio_context
    • Spray mach message có OOL mach ports descriptor kích thước 1 hoặc 2
    • Đặt entry đầu tiên là MACH_PORT_NULL để nó rơi vào kalloc.16 và làm io_issued trông như 0
    • Dùng lần free thứ hai để giải phóng mảng OOL mach ports
    • Spray dữ liệu kiểm soát vào kalloc.16 để thay mảng mach ports bằng fake pointer

Khác biệt ràng buộc giữa sandbox ứng dụng và racoon

  • Trên A7~A9(X), do không có PAN nên chỉ với mmapmlock đã có thể dereference địa chỉ userland như thể đó là con trỏ kernel
  • Nhóm tác giả cũng muốn hỗ trợ A10 và A11, nhưng trong sandbox ứng dụng họ không tìm được rò rỉ địa chỉ kernel phù hợp và mục tiêu đặt dữ liệu kiểm soát để hoàn thiện khai thác
  • Các 1day dự định tận dụng gồm rò rỉ thông tin kernel stack của Ian Beer và sandbox escape qua backboardd
    • Kế hoạch là leak con trỏ shared memory hoặc đặt dữ liệu vào phân đoạn kernel __DATA
    • Vì không tìm được target phù hợp nên đường khai thác trong ứng dụng đã không hỗ trợ được A10/A11
  • Đường racoon thì có điều kiện khác
    • Nó chạy bằng root nhưng lại có sandbox chặt hơn ứng dụng thông thường
    • Do không truy cập được IOSurface nên không thể dùng spray OSUnserializeXML thông thường qua IOSurface::setValue
    • Thay vào đó, có thể dùng lời gọi OSUnserializeXML bên trong RootDomainUserClient::secureSleepSystemOptions để spray một số đối tượng theo kiểu leak
  • racoon có profile sandbox cho phép đọc và ghi mọi sysctl, đồng thời có quyền root
    • Nếu biết kernel slide thì có thể dùng sysctl global trong kernel __DATA như một kho dữ liệu ở địa chỉ đã biết
    • Trong Spice, lựa chọn là vm.swapfileprefix
  • Để lấy kernel slide, nhóm dùng CVE-2018-4413 của panicall
    • Rò rỉ thông tin trong sysctl_procargsx có thể leak gần trọn một trang bộ nhớ kernel chưa khởi tạo của kernel_map
    • Nhờ đó lấy được con trỏ kernel code và heap để xử lý A7~A11
    • Trong sandbox ứng dụng, sysctl_procargsx bị chặn nên không thể làm tương tự

Cấu trúc exploit tachy0n cho unc0ver

  • Mục tiêu của unc0ver là A8~A13, nên không thể bỏ qua A10+ hoặc dựa vào dereference userland
  • Exploit được thiết kế theo cấu trúc hai tầng để tính đến giai đoạn memory corruption có thể thất bại
    • Tầng dưới chạy các luồng freerer gọi lio_listio và các luồng racer unserialize OSData qua IOSurface
    • Mặc định có 4 freerer và 16 racer, có thể điều chỉnh
  • Dữ liệu được unserialize qua IOSurface là một OSDictionary chứa nhiều entry OSData
    • Vị trí tương ứng với io_issued phải là 0
    • Các magic value như 0x41414141, 0x69696969 và giá trị key k được dùng để phát hiện chồng lấn
  • Sau race, exploit kiểm tra mọi giá trị OSData
    • Đối tượng có magic value bị đổi được coi là đã bị đối tượng hệ thống khác chiếm và được đánh dấu để dọn dẹp sau
    • Nếu key và giá trị k trong buffer khác nhau thì suy ra đã có chồng lấn, tức một OSData khác đang trỏ tới cùng backing buffer
  • Các hàm maybe_reyoinkoverlap trong mã dùng thông tin này để dựng trạng thái chồng lấn rồi chuyển cho tầng trên
  • Tầng trên dùng các đối tượng OSData chồng lấn để tạo fake mach port
    • Free một OSData
    • Spray mach message có OOL port descriptor
    • Free OSData còn lại
    • Cấp phát lại bằng OSData mới chứa con trỏ fake task port

Đặt dữ liệu kiểm soát tại địa chỉ kernel đã biết

  • Exploit có thể đọc lại nội dung được tái cấp phát dưới dạng mảng OOL ports descriptor qua OSData, nhờ đó leak raw kernel pointer của mach port
  • Ở các bước sau, kỹ thuật này còn được dùng để leak địa chỉ task port và service port của IOSurfaceRoot, nhưng vấn đề then chốt là làm sao lấy được ổn định địa chỉ kernel của buffer có thể điều khiển
  • Ứng viên tìm thấy trong mã nguồn XNU là IOMemoryDescriptor
    • Trường _ranges là một mảng IOVirtualRange, và một IOVirtualRange đơn lẻ vừa khít trong kalloc.16
    • Tuy nhiên IOMemoryDescriptor thông thường nếu chỉ có một range thì dùng _singleRange thay vì heap allocation
  • IOBufferMemoryDescriptor là ngoại lệ vì với một range nó vẫn gọi IONew(IOAddressRange, 1) để cấp phát trên heap
  • Vị trí tiện lợi để cấp phát tùy ý và map nó vào không gian địa chỉ người dùng là giao diện AGX của IOAcceleratorFamily2
    • Mở userclient type 0 trên IOGraphicsAccelerator2 sẽ nhận được IOAccelContext2
    • Có thể map ba memory descriptor qua ::clientMemoryForType()
    • Type 0 có kích thước 0x8000 byte nên được dùng để nhận diện victim descriptor
  • Exploit dùng vòng lặp sau
    • Mở IOAccelContext2 và lấy hai OSData chồng lấn
    • Free một OSData
    • Kết nối IOAccelSharedUserClient2 đã mở trước đó bằng IOConnectAddClient()
    • Đọc OSData còn lại để kiểm tra xem 8 byte đầu có phải kernel pointer căn theo page và 8 byte tiếp theo có phải 0x8000 hay không
    • Nếu không khớp thì đóng IOAccelContext2 và lặp lại

Pageable memory, fake port và zone_require

  • Ngay cả khi đã map memory descriptor vào tiến trình và biết địa chỉ kernel của nó, vẫn còn vấn đề là vùng nhớ được tạo dưới dạng kIOMemoryPageable
  • Fake mach port và fake task object có thể bị truy cập khi preemption bị tắt, nên kernel phải fault-in được trang đó trước
  • Điều này được xử lý bằng cách gọi hai lần external method 2 IOAccelContext2::submit_data_buffers, vốn gián tiếp gọi IOAccelContext2::processSidebandBuffer
    • Nó đọc cấu trúc nằm sau 0x10 byte kể từ đầu shared memory
    • Cấu trúc đầu tiên có tok == 0x100 và được dựng để bao phủ toàn bộ trang, khiến xử lý tiếp sang trang thứ hai
    • Trang thứ hai sau đó có thể chứa dữ liệu fake object
  • Các bước tiếp theo dẫn tới fake task, fake port, OOL descriptor switcheroo và dựng primitive đọc tùy ý
  • Cũng cần vượt qua zone_require
    • Khi đó zone_require chấp nhận các trang ngoài zone_map và diễn giải 0x20 byte đầu trang như metadata
    • Nếu điền đúng zone index thì có thể dùng nó như giấy thông hành cho zone mong muốn
    • Vì vậy cần hai trang: một cho task và một cho mach port
  • Exploit này hiện đã được công bố trên GitHub

Phân tích và vá lỗi sau công bố

  • Việc công bố một exploit 0day hoàn chỉnh cho phiên bản đang còn được ký mới nhất đã thu hút sự chú ý lớn trong giới jailbreak iOS
  • Brandon Azad, khi đó đang làm ở Project Zero, đã xác định lỗ hổng và báo cho Apple chỉ trong vòng 4 giờ sau khi exploit được công bố
  • 6 ngày sau khi exploit được công bố, Synacktiv đăng bài viết mới cho rằng bản fix gốc trên iOS 12 đã tạo ra memory leak, và nỗ lực sửa memory leak đó có thể đã làm bug ban đầu sống lại
  • 9 ngày sau khi exploit được công bố, Apple phát hành bản vá
  • Sau đó, XNU được bổ sung kiểm thử hồi quy cho lỗi này
  • 54 ngày sau công bố, phiên bản reverse-engineer mang tên “tardy0n” được đưa vào Odyssey jailbreak, cũng nhắm tới iOS 13.0~13.5

Môi trường exploit thay đổi sau iOS 14

  • iOS 14 cho thấy sự thay đổi trong chiến lược bảo mật kernel của Apple
  • Trước iOS 14, dù primitive ban đầu là heap overflow, C++ object over-release hay type confusion thì target tiếp theo phần lớn vẫn là mach port
  • Một trong những thay đổi lớn nhất ở iOS 14 là allocator kalloczalloc
    • zone map được chia thành nhiều vùng “kheap”
    • dữ liệu do người dùng kiểm soát và đối tượng kernel được tách sang các heap khác nhau
    • với đối tượng kernel, Apple áp dụng sequestering để một trang địa chỉ ảo từng cấp phát cho một zone cụ thể sẽ không bị tái sử dụng cho zone khác trước khi reboot
    • bộ nhớ vật lý có thể được giải phóng, nhưng khoảng địa chỉ ảo không bị tái dùng cho đối tượng khác, từ đó gần như chặn hẳn type confusion trên đối tượng kernel
  • Cùng với guard page, vị trí bắt đầu allocation của zone thay đổi theo mỗi lần khởi động, và các tinh chỉnh sau đó, độ tin cậy của tấn công cross-zone bị giảm mạnh
  • Apple đã chuyển từ việc chỉ chặn lỗi riêng lẻ sang chặn cả chiến lược exploit
    • Nếu exploit dùng kmsg struct làm mục tiêu corruption thì cấu trúc đó sẽ được ký
    • Nếu dùng pipe buffer như giao diện đọc/ghi kernel ổn định thì các con trỏ liên quan sẽ được bảo vệ bằng PAC
    • Nếu xuất hiện kiểu dùng một đối tượng không liên quan làm victim thì loại đối tượng đó sẽ được hardening
  • Kết quả là trong phát triển exploit, chiến lược exploit trở nên có giá trị hơn cả 0day memory corruption ban đầu

Khoảng trống trong tri thức công khai

  • Trước iOS 14, tri thức công khai về nghiên cứu bảo mật iOS được đánh giá là gần tương đương với tri thức riêng tư
  • Sau iOS 14, ngoại trừ một vài ngoại lệ, việc chia sẻ thông tin gần như dừng hẳn
  • Ngay cả khi chỉ còn vài tuần trước iOS 19 beta, vẫn chưa có exploit kernel công khai cho iOS 18 hoặc iOS 17
  • Ghi chú bảo mật của Apple thỉnh thoảng vẫn đề cập các lỗ hổng bị khai thác ngoài thực tế, nhưng thông tin công khai không còn theo kịp nghiên cứu riêng tư
  • Việc tachy0n mới chỉ được công bố cách đây 5 năm cho thấy lĩnh vực exploit kernel iOS đã thay đổi nhanh đến mức nào

2 bình luận

 
ndrgrd 2025-05-26

Phần cứng của Apple đúng là rất xuất sắc, nhưng phần mềm thì đầy rẫy ý đồ muốn trói buộc người dùng.
Ngay cả khi chỉ muốn chạy một ứng dụng do chính mình tạo và build trên đúng thiết bị của mình, bạn cũng cần một gói đăng ký 100 đô la.

Nếu là lập trình viên dùng các ứng dụng mã nguồn mở quy mô nhỏ đến vừa và tự build để dùng,
thì trên thiết bị Apple, thay vì phải lợi dụng lỗ hổng để jailbreak rồi sideload, cứ dùng Android còn thoải mái hơn.

 
GN⁺ 2025-05-25
Các ý kiến trên Hacker News
  • Điều ấn tượng là cách họ đánh bại một công ty nghìn tỷ đô la lại là một công việc đơn giản và nhàm chán mà Apple đặc biệt yếu: kiểm thử hồi quy.
    SockPuppet, một trong những lỗ hổng lớn được dùng để jailbreak trên iOS 12, do Ned Williamson của Project Zero tìm ra và báo cáo cho Apple, được vá trong iOS 12.3, rồi sau đó được công khai trên trình theo dõi lỗi của Project Zero.
    Nhưng đến iOS 12.4, nó xuất hiện trở lại như thể chưa từng được vá; có lẽ Apple đã fork XNU thành một nhánh riêng cho phiên bản đó và không áp dụng bản vá.
    Đây là tín hiệu rất mạnh cho thấy không có kiểm thử hồi quy cho loại lỗ hổng này, và chỉ cần tự động hóa vài 1-day đã biết là Pwn có thể đánh trúng ngay.
    Tôi tự hỏi có bao nhiêu nơi vận hành các CI farm liên tục chạy lại những lỗ hổng cũ trên các phiên bản mới của những dự án như Linux, FreeBSD, OpenWRT, OpenSSH.

    • Kiểm thử hồi quy là quy trình QA tiêu chuẩn để xác nhận rằng lỗi đã sửa không tái xuất hiện.
      20 năm trước, hồi còn học đại học, tôi từng làm QA tình nguyện cho Mozilla; họ có một bộ kiểm thử hồi quy ngày càng lớn, chủ yếu xoay quanh các lỗi rendering/layout hoặc lỗi của engine JavaScript.
      Vì phải tạo các test case tối thiểu để tái hiện và xác nhận bản sửa, nên cũng dễ đưa chúng vào build pipeline.
      Lỗi là điều không thể tránh khỏi, nhưng kịch bản tệ nhất là những lỗi đã tốn thời gian và tiền bạc để sửa lại sống dậy.
      Các tổ chức coi trọng chất lượng chắc chắn đầu tư vào kiểm thử hồi quy, nhưng nhiều tổ chức không tôn trọng QA, hoặc không làm hẳn, hoặc giao outsource giá rẻ nhất.
      Việc Apple không có kiểm thử hồi quy cho jailbreak, vốn về mặt lịch sử là một trong những nhóm lỗi được chú ý nhất, thật sự rất lạ.
      Mozilla ngày nay có thể bị chỉ trích theo nhiều cách, nhưng ngay từ đầu những năm 2000 họ đã vận hành QA và CI/CD khá vững chắc bằng các công cụ như Tinderbox và Bugzilla.
      Khi DevOps trở thành trào lưu và phổ biến hóa cách làm này, tôi từng nghĩ chẳng phải mọi người đã làm vậy rồi sao, nhưng đó là nhầm lẫn của tôi.
    • Nếu “dự án” ở đây bao gồm cả cơ quan tình báo, thì có thể an toàn cho rằng ít nhất các cơ quan tình báo G10, cùng Nga, Trung Quốc, Triều Tiên và vô số nhóm tư nhân đang làm việc này.
    • Vấn đề gốc rễ dường như là nhiều tổ chức đã cô lập công việc bảo mật thành một luồng riêng và một phân loại lỗi riêng.
      Đó giống như một tình huống theo định luật Conway do sự tách biệt giữa bảo mật và phát triển tính năng tạo ra.
      Ngay cả khi có quy trình build/release và bộ kiểm thử hồi quy trưởng thành, do cấu trúc tổ chức nội bộ, rất có thể các vấn đề bảo mật kiểu này không được đưa vào đó.
    • Tôi không nhớ tên, nhưng từng thấy một dự án FOSS có thư mục test case cho từng issue.
      Dễ phải có hàng nghìn cái, và có lẽ đó là SQLite.
      Đây là cách đáng học hỏi.
      Nếu không backport bản sửa, có vẻ nhiều khả năng họ cũng sẽ không backport bài kiểm thử.
  • Khi thấy những cụm như kheap separation, task port mitigation, SSV, SPTM, tôi có cảm giác như đang nói chuyện với bạn bằng ngoại ngữ khá trôi chảy rồi đột nhiên chuyển sang giải thích phẫu thuật não hay vật lý hạt nhân, và mức hiểu biết rơi thẳng xuống vực.
    Lần tôi cố phiên dịch một cuộc trò chuyện về đại tu lò luyện kim cũng tương tự.
    Thật tiếc là jailbreak giờ không còn sôi động như trước.
    Tôi gần như chẳng làm gì thực dụng với chiếc iPad đã jailbreak, nhưng nó rất vui; nếu là bây giờ, tôi muốn cài một ứng dụng tethering, UTM và một giải pháp JIT.
    SideStore cũng có vẻ hứa hẹn, nhưng tài khoản của tôi từng là tài khoản Apple Developer trả phí nên còn 10 app ID không hết hạn, vì thế tôi không thể cài các app như UTM nếu không tạo tài khoản mới hoặc trả tiền lại.

    • Tôi từng jailbreak chiếc iPhone 4 cũ để dùng, và đó gần như là cách duy nhất để có thể dùng iPhone làm thiết bị chính.
      Sau khi mất điều đó, tôi quay lại Android; đến lúc ấy Android đã bắt kịp khá nhiều về các tính năng cơ bản.
  • Tôi nghe nói Apple hiện trả 1 triệu đô la cho jailbreak, và đó hẳn là mức sàn của giá thị trường tự do.

  • Nếu chuyện này là thật thì Apple đã dùng một chiến lược đáng kinh ngạc.
    Nếu khóa mọi con đường để lấy root trên thiết bị, Apple có thể vá các lỗ hổng mà nhà phát triển jailbreak tìm ra miễn phí.

    • Nhưng không hoàn toàn như vậy.
      Theo bài viết, cộng đồng kín vẫn có exploit và Apple vẫn vá chúng.
      Có vẻ chỉ cộng đồng công khai hoặc phía nhà phát triển này, vì lý do nào đó, không còn như vậy nữa.
  • Câu tôi thích nhất trong toàn bài là: “Tôi cũng muốn cảm ơn người nào đó đã unpatch lỗi đó trong iOS 13.0. Đó cũng là một việc làm rất tuyệt.”

  • “Tôi không thể tưởng tượng 5 năm nữa chúng ta sẽ ở đâu”, nhưng tôi thì tưởng tượng được.
    iMessage vẫn sẽ cho phép chiếm thiết bị, tài khoản và dữ liệu.

  • Bài không nói đây là tethered hay untethered.

    • tachy0n là leo thang đặc quyền cục bộ (LPE), nên cách phân loại đó không thật sự phù hợp.
      Jailbreak unc0ver được phân phối kèm cái này, theo tôi biết, có lẽ là “semi-untethered”.