Vụ jailbreak 0day cuối cùng: Tachy0n
(blog.siguza.net)- 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ượngkalloc.16hai 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
racooncó 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ùngIOBufferMemoryDescriptorvàIOAcceleratorFamily2để 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ấu trúc này có các trường
- 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_completionsẽ 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_listiophả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_listiolại tiếp tục kiểm tralio_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_listiocấp phátlio_context- Tác vụ hoàn tất và
do_aio_completiongiải phónglio_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_listionhì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_contextnằ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_issuedkhô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àokalloc.16và làmio_issuedtrô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
- Kích hoạt lần free đầu tiên của
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
mmapvàmlockđã 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
- Kế hoạch là leak con trỏ shared memory hoặc đặt dữ liệu vào phân đoạn kernel
- Đường
racoonthì 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
OSUnserializeXMLthông thường quaIOSurface::setValue - Thay vào đó, có thể dùng lời gọi
OSUnserializeXMLbên trongRootDomainUserClient::secureSleepSystemOptionsđể spray một số đối tượng theo kiểu leak
racooncó 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
__DATAnhư một kho dữ liệu ở địa chỉ đã biết - Trong Spice, lựa chọn là
vm.swapfileprefix
- Nếu biết kernel slide thì có thể dùng sysctl global trong kernel
- Để lấy kernel slide, nhóm dùng CVE-2018-4413 của panicall
- Rò rỉ thông tin trong
sysctl_procargsxcó thể leak gần trọn một trang bộ nhớ kernel chưa khởi tạo củakernel_map - Nhờ đó lấy được con trỏ kernel code và heap để xử lý A7~A11
- Trong sandbox ứng dụng,
sysctl_procargsxbị chặn nên không thể làm tương tự
- Rò rỉ thông tin trong
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_listiovà các luồng racer unserializeOSDataqua IOSurface - Mặc định có 4 freerer và 16 racer, có thể điều chỉnh
- Tầng dưới chạy các luồng freerer gọi
- Dữ liệu được unserialize qua IOSurface là một
OSDictionarychứa nhiều entryOSData- Vị trí tương ứng với
io_issuedphải là 0 - Các magic value như
0x41414141,0x69696969và giá trị keykđược dùng để phát hiện chồng lấn
- Vị trí tương ứng với
- 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ị
ktrong buffer khác nhau thì suy ra đã có chồng lấn, tức mộtOSDatakhác đang trỏ tới cùng backing buffer
- Các hàm
maybe_reyoinkvàoverlaptrong 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
OSDatachồng lấn để tạo fake mach port- Free một
OSData - Spray mach message có OOL port descriptor
- Free
OSDatacòn lại - Cấp phát lại bằng
OSDatamới chứa con trỏ fake task port
- Free một
Đặ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
_rangeslà một mảngIOVirtualRange, và mộtIOVirtualRangeđơn lẻ vừa khít trongkalloc.16 - Tuy nhiên
IOMemoryDescriptorthông thường nếu chỉ có một range thì dùng_singleRangethay vì heap allocation
- Trường
IOBufferMemoryDescriptorlà ngoại lệ vì với một range nó vẫn gọiIONew(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
IOGraphicsAccelerator2sẽ nhận đượcIOAccelContext2 - 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
- Mở userclient type 0 trên
- Exploit dùng vòng lặp sau
- Mở
IOAccelContext2và lấy haiOSDatachồng lấn - Free một
OSData - Kết nối
IOAccelSharedUserClient2đã mở trước đó bằngIOConnectAddClient() - Đọc
OSDatacò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ải0x8000hay không - Nếu không khớp thì đóng
IOAccelContext2và lặp lại
- Mở
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ọiIOAccelContext2::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 == 0x100và đượ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_requirechấp nhận các trang ngoàizone_mapvà 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
- Khi đó
- 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ố
- Phân tích này được tổng hợp trong How to unc0ver a 0-day in 4 hours or less
- 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
kallocvàzalloc- 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
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.
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.
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.
Đó 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 đó.
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.
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.
Không rõ phải đi qua môi giới, hay có email công khai nào đó không bị chôn vùi nếu hỏi qua tuyến hỗ trợ khách hàng cấp một.
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í.
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.
Jailbreak unc0ver được phân phối kèm cái này, theo tôi biết, có lẽ là “semi-untethered”.