Gỡ lỗi một ứng dụng không thể gỡ lỗi
(bryce.co)- Reverse engineering ứng dụng iOS đòi hỏi khả năng quan sát và thao tác ứng dụng đang chạy, nhưng ứng dụng widget này dùng đồng thời chặn debugger, chặn code injection và phát hiện jailbreak
- Cơ chế chặn cốt lõi có thể được triển khai bằng PT_DENY_ATTACH của
ptracehoặc một syscall trực tiếp có hiệu ứng tương tự (svc #0x80), nên chỉ đặt breakpoint đơn giản vàoptracesẽ không bắt được - Syscall trực tiếp được vượt qua bằng cách tìm mẫu
mov w16, #26trong binary, đặt breakpoint tại vị trísvc, rồi dùnglldb jumpđể bỏ qua lệnh đó - Hành vi soft-reboot/respring điện thoại sau khi phát hiện jailbreak được liên kết với một hàm gọi
snapshotViewAfterScreenUpdates:trong vòng lặp vô hạn, và được bỏ qua bằngthread returntại điểm bắt đầu hàm - Crash sau code injection có vẻ gần với việc mất quyền App Group hơn là một kiểm tra framework runtime riêng; bằng cách swizzle
containerURLForSecurityApplicationGroupIdentifier:để trỏ tới thư mục tạm, ứng dụng có thể chạy được cả trên thiết bị thông thường
Ứng dụng widget iOS chồng nhiều lớp bảo vệ
- Đối tượng là một ứng dụng widget trên App Store, có các hành vi phòng vệ mạnh hơn ứng dụng widget thông thường
- Chặn debugger attach
- Thoát ứng dụng khi có code injection
- Nếu chạy trong trạng thái jailbreak, toàn bộ điện thoại sẽ soft-reboot/respring
- Việc ứng dụng iOS đưa vào các cơ chế bảo vệ như phát hiện jailbreak hay làm rối mã không phải hiếm, nhưng ứng dụng này dùng nhiều cách cùng lúc
- Các hành vi thú vị khác bên trong ứng dụng được để lại làm chủ đề cho bài viết sau
Chặn debugger attach bằng PT_DENY_ATTACH
- Trên thiết bị jailbreak, thường có thể kết nối bằng
ssh, chạydebugserver, rồi kết nối từlldbtrên một máy tính khác để gỡ lỗi ứng dụng - Khi gắn
debugservervào ứng dụng này theo cùng cách, xảy ra Segmentation fault và attach thất bại - Nguyên nhân liên quan đến request
PT_DENY_ATTACHcủaptraceptracelà private API trên iOS và public API trên macOSPT_DENY_ATTACHđặt một flag từ chối việc trace của tiến trình cha về sau- Nếu đang bị trace sẵn, ứng dụng sẽ thoát với trạng thái
ENOTSUP - Nếu tiến trình cha cố trace một tiến trình đã đặt flag này, phía tiến trình cha sẽ gặp segmentation violation
- Cách triển khai đơn giản có thể là gọi
ptrace(PT_DENY_ATTACH, 0, 0, 0)- Vì là iOS private API, khi gọi thực tế cần tìm symbol
ptracetronglibsystem_kernel.dylibbằngdlopen,dlsym - Cách này tương đối dễ vượt qua bằng cách đặt breakpoint vào
ptracerồi dùngthread returnđể bỏ qua lời gọi
- Vì là iOS private API, khi gọi thực tế cần tìm symbol
Vì sao cách vượt đơn giản không hiệu quả
PT_DENY_ATTACHchỉ chặn debugger sau khi được gọi, nên nếu attach trước khi code ứng dụng chạy thì có thể tạo điểm vượt qua- Thay vì gắn
debugservertrực tiếp vào tiến trình, có thể chạy nó trước rồi tronglldbdùngprocess attach --name TopWidget --waitforđể chờ ứng dụng khởi chạy, nhờ đó attach trước khi code ứng dụng chạy - Tuy nhiên trong ứng dụng này, breakpoint
b ptraceban đầu không được resolve, và kể cả sau đó khi được resolve thì ứng dụng vẫn thoát mà breakpoint không hề bị hit - Lý do là ứng dụng đã dùng syscall trực tiếp có cùng hiệu ứng thay vì gọi hàm
ptrace
Tìm vị trí syscall trực tiếp
- Trong disassembly của hàm
ptrace, về cốt lõi có syscallsvc #0x80x0chứa giá trị31, tứcPT_DENY_ATTACHx1,x2,x3chứa các tham số không dùng đến là0x16chứa số syscall củaptrace, là26
- Ứng dụng có thể không gọi hàm
ptrace, mà tự thiết lập cùng các giá trị thanh ghi bằng inline assembly rồi thực thi trực tiếpsvc #0x80- Cách này tránh các lookup private API đáng nghi như
dlopen,dlsym - Khó bắt bằng cách đặt breakpoint vào hàm chung
ptrace
- Cách này tránh các lookup private API đáng nghi như
- Để vượt qua, cần mở binary ứng dụng đã được giải mã bằng disassembler và tìm vị trí syscall
ptrace - Mục tiêu tìm kiếm là
mov x16, #26hoặc view 32-bit của cùng thanh ghi làmov w16, #26- Có thể dùng armconverter.com để lấy byte
50 03 80 D2củamov x16, #26và dùng để tìm trong binary mov x16, #26không cho kết quả, còn tìmmov w16, #26cho 4 kết quả
- Có thể dùng armconverter.com để lấy byte
- Trong đó, hai kết quả có các lệnh xung quanh không khớp với mẫu dự kiến; ở kết quả thứ ba, đoạn code dạng sau được xác nhận
MOV X0, #0x1FMOV X1, #0MOV X2, #0MOV X3, #0MOV W16, #0x1ASVC 0x80
- Kết quả thứ tư là một branch khác của cùng hàm, và hàm này được xác nhận là vị trí chặn debugger attach
Bỏ qua lệnh svc
- Các địa chỉ
svcđược xác nhận trong disassembler là0x102A2BB14và0x102A2BB68 - Trong
lldb, để chuyển địa chỉ theo binary thành địa chỉ load thực tế, đặt breakpoint kèm-s TopWidgetbr s -a 0x102A2BB14 -s TopWidgetbr s -a 0x102A2BB68 -s TopWidget
- Khi tiếp tục chạy, breakpoint sẽ dừng tại vị trí
svc #0x80 - Cách vượt dễ nhất là jump tới địa chỉ lệnh tiếp theo để không thực thi chính syscall
- Trong ví dụ, chạy
jump *0x10327bb18tới địa chỉ ngay sau lệnh hiện tại là0x10327bb18
- Trong ví dụ, chạy
- Sau quá trình này, có thể đi vào bên trong ứng dụng trong trạng thái debugger đã attach
Hành vi soft-reboot điện thoại
- Ngay cả sau khi vượt qua việc debugger attach, ứng dụng vẫn thực thi hành vi bảo vệ khiến điện thoại soft-reboot/respring
- Lần này
lldbvẫn đang attach, nên có thể kiểm tra trạng thái tiến trình nhậnSIGKILLvà stacktrace - Stacktrace cho thấy luồng chụp nội dung màn hình
CARenderServerSnapshotcủaQuartzCore_UISnapshotScreenWindowsRectAfterCommitcủaUIKitCore- unnamed symbol trong ứng dụng
TopWidget
- Dùng
lldb image lookupđể chuyển địa chỉ runtime thành địa chỉ theo binary0x100041898, rồi kiểm tra hàm tương ứng trong disassembler - Kết quả decompile cho thấy hàm chỉ lặp lại các tác vụ sau trong vòng lặp vô hạn
- Gọi
+[UIScreen mainScreen] - Gọi
snapshotViewAfterScreenUpdates:trên đối tượng màn hình trả về - Kết quả không được dùng và bị release
- Gọi
snapshotViewAfterScreenUpdates:là public API để tạo view snapshot, nhưng ứng dụng này lặp vô hạn một lời gọi tiêu tốn nhiều bộ nhớ- Trong video liên quan, nguồn gốc của lời gọi này được xác nhận là liên quan đến notification
com.apple.tw.twrr, và nếu không vượt qua risk check với điện thoại thì sẽ bị respring - Có thể vượt qua bằng cách đặt breakpoint tại điểm bắt đầu hàm đó và khi hit thì dùng
thread returnđể bỏ qua việc thực thi hàm
Crash khi code injection
- Khi gỡ lỗi, có thể triển khai các tiện ích phức tạp như logging thông tin accessibility của các nút trên màn hình trong một framework được inject vào ứng dụng, rồi gọi từ debugger
- Ngay cả khi không có thiết bị jailbreak, có thể inject Frida hoặc Flex để khảo sát ứng dụng ban đầu
- Thông thường kiểu injection này được thực hiện bằng các công cụ resign ứng dụng; trong ví dụ, Sideloadly được dùng
- Ứng dụng này crash ngay khi chạy sau khi resign
- Nhìn địa chỉ trên cùng của crash stack
0x1002027D4trong disassembler sẽ thấy lệnhBRK, khớp với tình huống crash có chủ ý như force-unwrap nil - Trong code đã decompile, lời gọi có vẻ gây vấn đề là
containerURLForSecurityApplicationGroupIdentifier:- Method này trả về URL thư mục mà ứng dụng và extension trong cùng group có thể cùng truy cập
- App Group được định nghĩa trong quá trình ký mã
- Khi resign sau code injection, chữ ký ứng dụng ban đầu bị loại bỏ và quyền App Group cũng mất
- Vì vậy có khả năng nó trả về
nilthay vì URL, rồi ứng dụng force-unwrap giá trị này và crash
- Vì ứng dụng widget cần chia sẻ App Group với extension phụ trách hiển thị widget trên màn hình chính, vấn đề này gần với lỗi ký mã phổ biến hơn là một hành vi có chủ đích để phòng vệ
Vượt qua vấn đề App Group
- Cách giải quyết dễ nhất là không resign ứng dụng
- Trên thiết bị jailbreak, có thể inject framework bằng jailbreak tweak mà không resign ứng dụng
- Cũng có thể chạy code có chữ ký không hợp lệ hoặc thêm ứng dụng mong muốn vào group mong muốn
- Nếu không có thiết bị jailbreak và vẫn cần resign, có thể vượt qua bằng cách swizzle method với một framework nhỏ
- Code ví dụ thay thế
containerURLForSecurityApplicationGroupIdentifier:củaNSFileManager- Khi method gốc được gọi, replacement method sẽ chạy
- Replacement method trả về
temporaryDirectorythay vì shared container
- Sự thay thế này không giống với hành vi ứng dụng vốn mong muốn
- Ứng dụng gốc muốn một thư mục dùng chung mà cả ứng dụng và extension đều có thể truy cập
- Thư mục tạm không phải là thư mục dùng chung như vậy
- Tuy nhiên, nếu mục tiêu chỉ là xem xét hoạt động của ứng dụng chính thì có thể là đủ
- Bản patch nhỏ như vậy có thể làm hỏng app extension
- Nếu chỉ cần các chức năng cơ bản của ứng dụng chính thì có thể không thành vấn đề
- Cách vượt lớn hơn là tạo App Group mới, resign ứng dụng chính và tất cả extension vào group đó, rồi swizzle các method liên quan để dùng group identifier mới
- Đây là công việc lớn hơn nhiều, và nếu không thật sự cần thì có lẽ nên kiếm một thiết bị jailbroken
- Khi inject framework này cùng một công cụ như
Flex, ứng dụng chạy bình thường cả trên thiết bị thông thường - Trên thiết bị jailbreak, vẫn cần vượt lại các cơ chế anti-debugging và bảo vệ respring ở trên, nhưng sau đó có thể đi vào trong ứng dụng với
Flexđã được inject
Trạng thái cuối cùng
- Cuối cùng, ứng dụng đạt trạng thái có thể debugger attach, vượt phát hiện jailbreak và code injection
- Việc thực tế muốn xem gì bên trong ứng dụng được để lại làm chủ đề cho bài viết tiếp theo
1 bình luận
Ý kiến trên Hacker News
Bryce Bostwick làm những việc thật sự rất hay và truyền cảm hứng trong mảng gỡ lỗi ứng dụng và kỹ nghệ đảo ngược
Tôi biết đến anh ấy trên YouTube, rồi sau khi xem video chỉnh sửa TikTok để chỉ hiện video mèo (https://youtu.be/YW3jL2gI9IE), tôi đã thử chỉnh Instagram để loại bỏ mọi thứ, chỉ giữ lại tính năng nhắn tin mà tôi dùng
Từ lâu tôi đã muốn đào sâu hơn cách chỉnh sửa Windows theo kiểu Windhawk(https://windhawk.net/), đặc biệt là modding và kỹ nghệ đảo ngược, và Bryce giới thiệu rất tốt những việc kiểu đó trên iOS bằng các video từng bước theo thời gian thực
Tôi đã thấy có thể làm được những thứ đáng kinh ngạc với Revanced, nhưng có vẻ không có nhiều hướng dẫn hay về cách bắt đầu làm những việc đó
Nếu có ai tổng hợp lại quy trình thì tôi rất quan tâm
Tôi ghét việc chỉ muốn đăng ảnh hoặc chat với bạn bè mà vẫn bị phơi trước Reels
Chống gỡ lỗi, thậm chí cả các kỹ thuật chặn lại việc vô hiệu hóa chống gỡ lỗi, đã phổ biến từ lâu ở phía DOS/Windows
Các tài liệu cracking hoặc unpacking cũ đề cập đến những nội dung này ở nhiều mức độ sâu khác nhau
Việc người dùng có thể dễ dàng kiểm soát hành vi của ứng dụng đến đâu tỉ lệ nghịch với mức độ nền tảng đó thù địch với người dùng
PT_DENY_ATTACH trông đúng như một tính năng được tạo ra vì sự thù địch với người dùng đó
Theo tôi biết thì Windows không có tính năng như vậy; thay vào đó người ta dùng kỹ thuật khiến ứng dụng tự attach vào chính nó
https://www.x86matthew.com/view_post?id=selfdebug
https://anti-debug.checkpoint.com/techniques/interactive.htm...
Hơi ngạc nhiên là quy trình kiểm duyệt App Store của Apple không từ chối các ứng dụng thực hiện lời gọi hệ thống trực tiếp
Trên nền tảng Apple, lời gọi hệ thống không phải ABI ổn định, nên mọi lời gọi hệ thống phải đi qua libSystem; ứng dụng gọi hệ thống trực tiếp mà không qua libSystem về cơ bản là đang làm điều không nên làm
Tương tự, tôi cũng tò mò vì sao ở đây tác giả lại tìm
mov w16, #26trong mã thay vìsvc 0x80svc 0x80là lệnh thực thi bất kỳ lời gọi hệ thống nào, còn chính xác lời gọi nào được thực thi thì do thanh ghi x16 quyết địnhỨng dụng sẽ thực hiện rất nhiều lời gọi hệ thống không liên quan, nên đặt breakpoint vào đó sẽ không hữu ích
Ít nhất trong video, họ giải thích như vậy
Cũng vì lý do đó, nếu tìm lệnh SVC thì sẽ ra cực kỳ nhiều kết quả
Nếu tìm đúng ID lời gọi hệ thống được chuyển vào X16 thì có thể phát hiện ngay
Tôi là tác giả bài viết. Nếu có câu hỏi thì tôi sẽ trả lời. Cảm ơn xmprt đã chia sẻ
Cũng rất vui vì có cả phiên bản bài viết
Tôi muốn hỏi tác giả vài điều: nếu công cụ thương mại nổi tiếng nhất là Guardsquare, liệu họ có cung cấp thứ gì mới để ngăn việc disassemble dễ dàng như thế này không
Tôi cũng tò mò liệu TopWidgets có dùng kiểu bảo vệ tương tự hay chỉ là mức tự làm
Cá nhân tôi dùng Android nên không áp dụng kỹ thuật trực tiếp được, nhưng vẫn học được rất nhiều giá trị từ việc hiểu gỡ lỗi mức thấp trên iOS hoạt động thế nào
Video ở đầu bài là một trong những video lập trình hay nhất tôi từng xem
Nhịp trình bày nhanh, giả định đúng mức kiến thức nền cần có, và có các phần demo rất tốt mà không làm đứt mạch video
Bài viết xuất sắc
Tôi thật sự tò mò đây là một ứng dụng bình thường nhưng hoang tưởng quá mức, hay là một ứng dụng vốn đã bị nghi là malware nên mới bị đem ra gỡ lỗi
Nếu không thì công sức bỏ ra có vẻ khá quá đà
Nó làm được những thứ khá hay với widget nên có lẽ họ muốn bảo vệ điều đó. Dù vậy, các chiến lược kiểu này rồi cũng bắt đầu rò rỉ dần
Trong binary cũng có vài thứ thú vị
Có lúc tôi cố tìm hiểu vì sao mình lại thấy đoạn mã trông như đang tải Windows
.iso, và hóa ra đúng là vậy; nó được dùng cho widget kiểm tra tốc độ mạngCòn có chế độ khó hơn cả “Vượt PT_DENY_ATTACH (hard mode)”
Trước đây trên macOS, tôi từng vá kernel để PT_DENY_ATTACH không làm gì cả
Trên Mac, chạy kernel đã vá thực ra khá dễ, nhưng trên iOS thì có lẽ rắc rối hơn nhiều vì những thứ như KTRR
XNU về mặt kỹ thuật là mã nguồn mở, nhưng vá bằng trình hex editor dễ hơn so với biên dịch lại
Có thể cho phép cả trang mã chưa ký và RWX để hỗ trợ JIT
Nếu “bật jailbreak rồi chạy là toàn bộ điện thoại crash”, chẳng phải nên báo cáo lên store là malware sao?
Làm điện thoại crash rõ ràng là hành vi của malware, và cũng nên nghi ngờ còn hành vi độc hại nào khác đang bị che giấu
Chẳng phải việc ngăn thứ rác rưởi như vậy là lý do biện minh cho hệ sinh thái đóng của Apple sao?
Tôi nghĩ Apple sẽ không mấy quan tâm việc ứng dụng crash trên điện thoại đã jailbreak
Tôi rất tò mò về thông báo com.apple.tw.twrr
Vì sao nó bắt đầu bằng
com.apple?Ứng dụng đang nói đến ở đây có vẻ không phải ứng dụng Apple mà là ứng dụng tên Top Widgets
Thông lệ là dùng một “tên đầy đủ” kiểu như vậy để tránh xung đột
Trong trường hợp này có lẽ nhà phát triển tình cờ chọn tiền tố đó
Chỉ khác công cụ thôi, chứ cảm giác này giống hệt việc phá bảo vệ sao chép Apple II bằng boot tracing hồi thập niên 1980
Có những thứ không thay đổi