TẢN MẠN VỀ THIẾT LẬP KHAI THÁC CHROME Maglev Edition
Sơ lược về Chromium security architecture
Trước khi đi sâu vào chi tiết của quá trình render và V8 engine, chúng ta bắt đầu với cách thức hoạt động của kiến trúc tổng thể của Chromium. Chromium sử dụng kiến trúc đa quy trình để phân tách các tác vụ khác nhau thành các quy trình khác nhau. Sự phân tách này làm tăng tính ổn định và bảo mật của trình duyệt vì các vấn đề trong một quy trình (như tab bị sập) không ảnh hưởng đến các quy trình khác. Hình ảnh Chromium Project bên dưới sẽ cung cấp tổng quan về màu sắc theo rủi ro của các thành phần của trình duyệt.
Kiến trúc bảo mật Chromium
Trong sơ đồ, chúng ta thấy một số loại quy trình riêng biệt:- Browser Process : Quản lý giao diện người dùng, đĩa và I/O mạng, và hoạt động như bộ điều phối trung tâm cho các quy trình khác. Nó hoạt động với đầy đủ đặc quyền của người dùng và quản lý hầu hết các tài nguyên hệ thống, bao gồm hồ sơ người dùng và các dữ liệu liên tục.
- Renderer Processes : Xử lý việc render các trang web và thực thi JavaScript. Mỗi trang web hoặc ứng dụng web thường chạy trong quy trình renderer riêng, tách biệt với các quy trình khác. Các quy trình này phải tuân theo các biện pháp giảm thiểu nghiêm ngặt — chẳng hạn như sandbox — để đảm bảo render an toàn nội dung web.
- GPU Process : Quy trình này xử lý các tác vụ GPU riêng biệt với việc hiển thị nội dung web, cải thiện hiệu suất và bảo mật. Nó được chạy với quyền tối thiểu, có vai trò xử lý các tác vụ đồ họa chuyên sâu một cách hiệu quả.
- Utility Process: Đóng vai trò xử lý các hoạt động ngắn hạn và có thể chạy sandboxed hoặc unsandboxed tùy thuộc vào chức năng cụ thể.
- PPAPI (Pepper Plugin API) Broker Process và NaCl (Native Client) Loader Process là hai thành phần trong kiến trúc của Chromium, quản lý các khía cạnh khác nhau khi chạy code trong trình duyệt.
Bây giờ chúng ta đi sâu vào chủ đề chính của bài viết này !!!
0. Intro V8 engine
Đầu tiên ta sẽ xem xét kỹ hơn về quy trình render trong Chromium. Quy trình render này rất quan trọng đối với bảo mật của trình duyệt vì nó chịu trách nhiệm vẽ các trang web và thực thi mã JavaScript không đáng tin cậy, khiến nó trở thành mục tiêu chính cho các cuộc khai thác. Điều này đưa chúng ta đến với key word mới là V8 engine. V8 được hiểu đơn giản chính là JavaScript và WebAssembly engine cung cấp cơ sở cho quy trình này, phân tích cú pháp và thực thi mã định nghĩa tương tác của người dùng và chức năng của trang web. Vì vậy, V8 ở đây có ảnh hưởng trực tiếp đến tính bảo mật và tính ổn định của quy trình renderer. Hãy cùng khám phá chi tiết hơn các thành phần khác nhau giúp cho đường truyền V8 Chromium JIT trở nên hiệu quả và xem xét cách mà tính phức tạp vốn có của nó có thể dẫn đến lỗi nhầm lẫn kiểu (Type confusion bugs).1. Các bước và yêu cầu thiết lập môi trường Testing V8
Từ Visual Studio Installer Visual Studio, Ta thiết lập 2 môi trường Workloads C++ và Python. Individual Components của window pane ta chuẩn bị:- C++ MFC for Latest v143 Build Tools (x86 & x64)
- C++ Clang Compiler for Windows (17.0.3)
- MSBuild support for LLVM (clang-cl) toolset
- C++ CMake tools for Windows
- Git for Windows
- Windows 11 SDK (10.0.22621.0)
2. Testing V8 Environment
Khi các điều kiện yêu cầu bên trên đã được đáp ứng, Chúng ta có thể tạo một thư mục như C:\v8_dev chứa các cài đặt tất cả thư viện phụ thuộc và mã nguồn cần thiết cho V8. Từ dấu nhắc lệnh, điều hướng đến thư mục đó và tải xuống Depot Tools thông qua gitclone.Tải xuống công cụ depot
Depot Tools là một tập hợp các script và công cụ được thiết kế để quản lý quy trình phát triển cho các dự án liên quan đến Chromium. Những công cụ này giúp trong việc lấy mã nguồn, quản lý các phụ thuộc, xây dựng dự án và chạy thử nghiệm. Một số công cụ trong đó bao gồm gclient để đồng bộ hóa các phụ thuộc, ninja để xây dựng, và gn để tạo dự án.
Sau đó, chúng ta có thể thêm thư mục công cụ depot vào biến hệ thống PATH cũng như hai biến người dùng DEPOT_TOOLS_WIN_TOOLCHAIN và vs2022_install để dễ dàng truy cập sử dụng.
Chúng ta có thể thực hiện nhanh bằng cách dán và chạy các lệnh sau trên Command promt admin.Thêm các biến môi trường cần thiết
Sau khi thiết lập các biến, chúng ta có thể di chuyển đến thư mục con depot tools và chạy lệnh gclient , và cùng đợi một lát để hoàn tất đồng bộ.
Chạy lệnh gclient
Lệnh gclient là một phần của Depot Tools và được sử dụng để quản lý và đồng bộ hóa các thư viện phụ thuộc cho Chromium và các dự án liên quan. Lệnh này lấy mã nguồn, kiểm tra các phiên bản thư viện phụ thuộc chính xác và thiết lập môi trường phát triển. Nếu quá trình đồng bộ không gặp lỗi, mặc định công cụ Python 3 của depot sẽ được ưu tiên sử dụng.Thứ tự ưu tiên của đường dẫn Python3
Từ thư mục v8_dev, bây giờ chúng ta có thể tạo thư mục con v8 và lấy mã nguồn V8 bằng lệnh fetch.
Đang lấy mã nguồn v8
Sau khi lấy mã nguồn của V8, chúng ta sẽ dựng lại phiên bản tồn tại lỗ hổng, cụ thể trong bài viết này là CVE-2023-4069. Theo báo cáo lỗi của Chromium đã ghi nhận , sự cố đã được thực hiện trong phiên bản 11.5.150.16 và được thử nghiệm trên commit 5315f073233429c5f5c2c794594499debda307bd . Vì vậy, chúng ta có thể dùng git checkout đến nó như sau từ thư mục v8 mới tạo.Xây dựng lab V8 khớp version CVE đề cập
Tiếp theo, chúng ta cần chạy lệnh gclient sync -D để xác minh rằng mã nguồn đang ở trạng thái Clean, xóa mọi tệp không được kho lưu trữ theo dõi và đảm bảo rằng tất cả các thành phần phụ thuộc đều khớp với Commit đã chỉ định.
Cập nhật các phần thư viện phụ thuộc với gclient
Sau khi cập nhật đúng mã nguồn tồn tại lỗ hổng và các thành phần liên quan, giờ là lúc biên dịch V8. Chúng ta sẽ thực hiện thông qua công cụ đóng gói của Python gm.py và chỉ định loại kiến trúc/bản dựng làm đối số. Vì trình biên dịch Maglev chỉ có trong phiên bản phát hành nên chúng ta phải chỉ định Flags: x64.release.
python3 tools\dev\gm.py x64.release
Compiling V8
Sau khi hoàn tất các bước trên, Chúng ta có thể thử khởi chạy V8 và xác minh version của nó là phiên bản d8 11.5.150.16 như hiển thị bên dưới. C:\v8_dev\v8\v8\out\x64.release\d8.exe --maglev3. Walk Through CVE-2023-4069
Vào tháng 10 năm 2023, Man Yue Mo từ nhóm bảo mật GitHub đã xuất bản một bài viết về lỗi CVE-2023-4069 Type Confusion trong V8. Tóm lại, Nguyên nhân chính của lỗi này trong trình biên dịch Maglev là do việc khởi tạo đối tượng không hoàn chỉnh. Trong quá trình xây dựng đối tượng, V8 có thể tạo ra một đối tượng chưa được khởi tạo hoàn chỉnh. Maglev có thể sử dụng những đối tượng này trước khi tất cả các thuộc tính của chúng được thiết lập, dẫn đến việc truy cập vào các đối tượng chưa được khởi tạo đúng cách. Điều này có thể gây ra lỗi bộ nhớ và dẫn đến lỗ hổng bảo mật. Trong JavaScript, từ khóa “new” được sử dụng để hỗ trợ tính năng Lập trình Hướng đối tượng (OOP). Khi sử dụng “new”, JavaScript tạo ra một đối tượng mới, thiết lập prototype của nó, liên kết “this” với đối tượng mới, và trả về đối tượng đó (trừ khi hàm khởi tạo này trả về một đối tượng khác). Ngoài ra, chúng ta cũng có một toán tử tương tự là new.target . Đây là special meta-property có sẵn trong các constructors. Nó cho phép chúng ta phát hiện xem một hàm hoặc hàm tạo có được gọi bằng toán tử new hay không . Nếu được gọi thông qua new, new.target sẽ tham chiếu đến constructor của function hoặc class đó. Nếu được gọi mà không có new , new.target sẽ trả về undefined. ⇒ Điều này giúp thực thi lệnh chỉ gọi các hàm tạo bằng new, ngăn chặn việc sử dụng sai mục đích khởi tạo đối tượng. Để làm rõ hơn những khái niệm này, đoạn mã dưới đây minh họa cách new.target hoạt động kết hợp với toán tử new.Ví dụ về toán tử 'new' và ‘new.target’ trong JS
Chúng ta có thể lưu lại đoạn code trên vào file new.js và thử load vào môi trường V8 để kiểm tra thực nghiệm như ảnh dưới đây.Thử nghiệm trong môi trường test V8
⇒ Như chúng ta có thể dự đoán, dòng đầu tiên in ra lỗi vì new.target bắt được exception do đối tượng được tạo mà không có toán tử new. Mặt khác, dòng thứ hai in đúng tham số vì đối tượng đã được khởi tạo bằng the_new_constructor. Dòng cuối cùng có thể không cần quan tâm vì đây là do đoạn mã trên không trả về giá trị nào nên mặc định xử lý của trình duyệt sẽ hiển thị “undefined”. Để tiếp cận sâu hơn nữa về lỗ hổng, chúng ta đi tiếp với reflect.construct nó có thể gián tiếp gọi new.target. Nó có cú pháp sau:
Reflect_construct.js
Và thật ngạc nhiên với output khi load đoạn code này để V8 xử lý:Load Reflect_construct.js
Trong hai dòng đầu tiên, chúng ta nhận được output của từng Class như mong đợi. Tuy nhiên, dòng thứ ba cho thấy chỉ có B được in sau khi gọi Reflect.construct. Tại sao vậy??? Vâng, khi Reflect.construct(A, [], B) được sử dụng, nó tạo ra new instance của A , nhưng nó đặt constructor’s new.target thành B. Điều này có nghĩa là trong constructor của A, new.target trỏ đến B mà không phải A. Vì vậy, mặc dù constructor của A được thực thi, new.target khiến nó in B thay vì A. Có một bật mí rất thú vị về Reflect.construct là nó thể hiện hành vi khác nhau tùy thuộc vào việc hàm được gọi có trả về giá trị hay không. Chúng ta hãy so sánh thông qua ví dụ của 2 đoạn code sau: Đầu tiên, chúng ta tạo một mục tiêu hàm trả về một mảng gồm ba số nguyên nhỏ và gọi nó thông qua Reflect.construct. Ta Load code trên vào V8 đi kèm flags:-allow-natives-syntax;
cho phép ta xem chi tiết về các đối tượng trong JS.
reflect.js

Output sau khi load reflect.js
Như dự đoán, hàm trả về một JSArray chứa 3 giá trị trên. Vậy điều gì sẽ xảy ra nếu chúng ta gọi một hàm mục tiêu và nó không trả về kết quả gì ?rc2.js
Output khi load vào V8 như hình bên dưới:
Output sau khi load rc2.js
Theo logic thông thường, khi chạy code trên chúng ta sẽ mong muốn chương trình trả về “undefined” từ console/debugger. Nhưng ở trong ví dụ trên, nó trả về “type: JS_OBJECT_TYPE”. Trong V8, khi tạo một đối tượng mới thông qua FastNewObject, một đối tượng mặc định (default receiver) cũng được tạo ra. Nếu hàm mục tiêu trả về một đối tượng khác, đối tượng mặc định sẽ bị bỏ qua và đối tượng được trả về sẽ được sử dụng; nếu không, đối tượng mặc định sẽ được trả về. Mỗi hàm JavaScript đều có một initial_map, đây là một đối tượng Map xác định loại và cấu trúc bộ nhớ của đối tượng nhận (receiver object). Như đã thảo luận trước đó, map đóng vai trò quan trọng để định nghĩa kiểu ẩn của một đối tượng, cấu trúc bộ nhớ và cách lưu trữ các trường. Cụ thể hơn:- initial_map: Đây là bản đồ (map) ban đầu của hàm, xác định kiểu ẩn và cách bố trí bộ nhớ cho các đối tượng được tạo từ hàm đó.
- FastNewObject: Khi hàm này được gọi để tạo một đối tượng mới, nó sử dụng initial_map của new.target để xác định cách tạo và bố trí bộ nhớ cho đối tượng mới.
- new.target: Đây là một meta-property trong JavaScript, cho biết hàm nào đã được sử dụng với từ khóa new. FastNewObject sử dụng initial_map của new.target để thiết lập đối tượng mặc định.
FastNewObject function
Nếu FastNewObject thất bại, nó sẽ gọi đường dẫn runtime JSObject::New:JSObject::New function
GetDerivedMap có thể gọi FastInitializeDerivedMap để tạo initial_map trong new_target:FastInitializeDerivedMap function
initial_map ở đây là bản sao của initial_map của target nhưng với prototype được đặt thành prototype của new_target và constructor được đặt thành target. Nói cách khác, mục đích của FastNewObject là kiểm tra rằng đối tượng nhận mặc định được tạo ra đúng cách và hiệu quả bằng cách sử dụng initial_map đúng. Khi tạo các đối tượng từ các Class với các constructor mặc định không hoạt động (no-op constructor), V8 tối ưu bằng cách bỏ qua các constructor này. Ví dụ:VisitFindNonDefaultConstructorOrConstruct
Hàm chịu trách nhiệm kiểm tra xem new_taget có phải là hằng số hay không chính là TryGetConstant. Ta hoàn toàn có thể bắt buộc new_target trở thành một hằng số khá đơn giản như đoạn code bên dưới.
Corrupting the Array After Triggering Garbage Collection
Sau khi trình tối ưu hóa Maglev được trigger thông qua việc ta dùng vòng lặp for , chúng ta in mảng thông qua câu lệnh debug. Sau đó, chúng ta kích hoạt garbage collection hai lần và in ra giá trị của mảng. Ở đây, Giá trị 0x3fe00000 (khoảng 1 GB) là giá trị phù hợp để trigger garbage collection vì nó đủ lớn để phân bổ bộ nhớ, lấp đầy một phần lớn bộ nhớ heap V8 là 4 GB và sẽ buộc công cụ JS thực hiện garbage collection. Chạy đoạn mã trên trong d8 sẽ hiển thị các thuộc tính mảng sau:References
- Wikipedia - JIT Compilation
- Chromium Blog - New Crankshaft for V8
- GitHub Security Lab Blog
- Google Docs - V8 Sandbox Design
- Jack Halon - V8 Exploitation Series
- Jeremy Fativeau - V8 Series
- Chrome Exploitation - Maglev Edition
- V8 Official Documentation - Maglev
- Microsoft Tech Community - Understanding Hardware-enforced Stack Protection
- V8 Official Documentation