Trong khoảng thời gian vừa qua, tại một số hội nhóm trên mạng xã hội Facebook , diễn đàn trực tuyến, nổi lên các bài viết của một số người dùng, liên quan đến vấn đề lừa đảo trực tuyến. Điểm chung của những bài viết này đều kể rằng họ là nạn nhân bị lừa đảo, họ bị dẫn dụ cài các ứng dụng mã độc, dẫn đến mất tiền trong tài khoản ngân hàng.
Những kẻ lừa đảo thường tiếp cận những người dùng này theo nhiều cách khác nhau như đóng giả thành nhân viên, nhắn tin với người dùng rằng họ cần nhanh chóng hoàn thiện một số thủ tục hành chính còn thiếu hoặc sửa lại các thông tin công dân bị sai lệch thông qua một ứng dụng có tên tệp tin là “Dichvucong.apk”, tải xuống và cài đặt từ một số website có tên miền như “dichvucong.lgov.net”, “dichvucong.dancuquocgia.net”,…
Ứng dụng giả mạo này sử dụng logo có hình quốc huy của quốc gia Việt Nam và tên là “DỊCH VỤ CÔNG” nhằm đánh lừa thị giác của người dùng.
Chúng tôi đã thực hiện kiểm thử và phân tích mã dịch ngược từ tệp tin “Dichvucong.apk” được tải xuống từ website “dichvucong.lgov.net”.
Từ mã dịch ngược của tệp tin “Dichvucong.apk”, trong những phân vùng code thực hiện khởi tạo, chúng tôi phát hiện ra một string là “virbox”.
“VIRBOX” là một cái tên khá nổi tiếng trong khoảng thời gian gần đây. Đây là một công nghệ bảo vệ ứng dụng, bao gồm các chức năng: anit-debug, anti-repacking, code obfuscation, code encryption, string encryption,… Công nghệ này đang được sử dụng bởi malware nổi tiếng khác như GoldDigger.
Mã dịch ngược của tệp tin “Dichvucong.apk” không thực hiện trực tiếp bất kì hành vi nguy hại nào ngoài việc khởi tạo môi trường cần thiết và tải động một thư viện đặt trong thư mục “assets” của ứng dụng.
Tùy vào kiến trúc mà thư viện được tải sẽ có suffix tương ứng trong tên, trên thiết bị mà chúng tôi sử dụng, ứng dụng sẽ tải động thư viện “l4eee8b4d_a32.so”
Tiến hành đọc file “index.html”, “kqkticwjgzy.dat” không thu lại được kết quả gì ngoài những chuỗi byte vô nghĩa. Tiếp theo, tiến hành dịch ngược thư viện, như kết quả dự đoán, phần lớn mã nguồn đã bị obfuscated và encryption, rất khó để reverse.
Để có thể đọc được mã nguồn ứng dụng “một cách rõ rang”, chúng ta cần dumped memory của tiến trình ứng dụng tại thời điểm ứng dụng đã được khởi chạy, khi đó mã nguồn thực sự đã được deobfuscated và decryption, các class java của ứng dụng được load vào memory.
Tiến hành dump các class trong memory của tiến trình ứng dụng:
Ta thu được mã nguồn dịch ngược như sau:
Từ mã nguồn dịch ngược, có thể nhận thấy ứng dụng khai báo một Accessiblity Service. Các dịch vụ trợ năng (Accessiblity Service) của Android nhằm hỗ trợ người dùng khuyết tật vận hành thiết bị của họ. Các dịch vụ này cung cấp các khả năng như đọc màn hình, phóng to, điều khiển dựa trên cử chỉ, chuyển giọng nói thành văn bản, phản hồi xúc giác và các tính năng khác.
Một số Trojan ngân hàng như Gustuff, Gigabud và GoldDigger đã và đang lợi dụng tính năng này để thực hiện các hành vi độc hại trên thiết bị của người dùng.
Tính năng này cho phép ứng dụng Trojan được cấp quyền Accessibility có thể quay lại màn hình, đọc được các đoạn thành phần văn bản trên màn hình, ghi chép lại thao thác gõ phím (keylogger), tự động điền văn bản,…
Ứng dụng độc hại có thể thu thập thông tin người dùng, quan sát số dư tài khoản ngân hàng, đánh cắp thông tin đăng nhập, ghi lại thông tin văn bản mà người dùng đã nhập,…
Máy chủ mà ứng dụng tương tác để lấy một số thông tin chung về các ngân hàng ở Việt Nam:
com.yiwuzhibo.BuildConfig
Một số API endpoint của máy chủ trên giúp ứng dụng lấy được các thông tin liên quan đến ngân hàng và người dùng ngân hàng Việt Nam:
k1.a
Kiểm tra một số endpoints nhận thấy máy chủ vẫn còn đang hoạt động và thông tin trả về liên quan đến các ngân hàng của Việt Nam.
Ứng dụng có các hành vi tiến hành tự động mở khóa màn hình:
com.yiwuzhibo.controller.LockManager$swipeUp$1
Tự động mở khóa màn hình
Khai báo các lớp nhằm thu thập các mật khẩu:
com.yiwuzhibo.controller.PasswordCapture2
Ứng dụng khai báo các lớp nhằm thực hiện tự động hóa các thao tác click các đối tượng trên màn hình thiết bị:
Ứng dụng khai báo các lớp nhằm điều khiển ứng dụng từ xa cũng như gửi dữ liệu thu thập đến máy chủ thông qua tcp:
com.yiwuzhibo.controller.RemoteCommandClient
com.common.want.WantCommonService
com.common.want.WantCommonService
Gửi một số thông tin thu thập được từ thiết bị tới máy chủ C&C
Ứng dụng khai báo các lớp hỗ trợ quay video màn hình:
com.yiwuzhibo.record.ScreenRecordService
Phần kỹ thuật đến đây thì cũng đã khá dài, vậy người dùng cuối cần làm gì???
Người dùng phải nâng cao nhận thức của bản thân, không cài đặt ứng dụng từ nguồn thứ ba không đáng tin cậy, luôn cập nhật thiết bị lên phiên bản mới nhất và chú ý ứng dụng đòi hỏi những permissions nào khi khởi chạy.
Hãy luôn cẩn trọng trước các lời mời gọi cài đặt app lạ!!!
Tiếp tục về SharePoint… Sau “cú dup” với CVE đầu tay, tôi tiếp tục đi sâu hơn vào chức năng này và tìm ra một flow khá hay ho. Đặc biệt hơn, flow này hoàn toàn dựa vào quá trình parse XML config để khởi tạo workflow – xin đính chính lại một chút là workflow config có định dạng là XAML.
Phân tích
Sau quá trình validate XAML config (đã nhắc đến ở phần 1) thì SharePoint sử dụng hàm WorkflowMarkupSerializer.Deserialize để tạo workflow. Không giống như XML đơn thuần, XAML cho phép sử dụng “compact format attribute”, các attribute này sẽ được bắt đầu bằng ký tự “{” và kết thúc bằng ký tự “}”.
Hàm DeserializeFromCompactFormat được sử dụng để xử lý trường hợp này. Đây là một hàm rất dài, tôi sẽ tóm tắt chức năng như sau:
Lấy kiểu dữ liệu của SomeType, kiểm tra xem kiểu dữ liệu này có nằm trong allow list hay không. Nếu không thì throw exception.
Đọc và parse các argument thành một mảng các chuỗi [“Arg1”, “Arg2”, “Arg3”].
Sử dụng reflect để tìm kiếm một constructor của SomeType với số lượng tham số và kiểu dữ liệu phù hợp (3 tham số chuỗi).
Tạo và trả về instance của SomeType.
Trace bằng dnSpy chúng ta sẽ được flow:
Nội dung của các hàm sẽ như sau.
Hơi dài dòng và khó hiểu nhưng tóm gọn lại thì nếu SomeType kế thừa MarkupExtension thì ValueType = (new SomeType).ProvideValue(), ngược lại thì ValueType = new SomeType.
Như đã nếu ở phần 1, ValueType sẽ mang giá trị là chuỗi System.Resources.ResourceSet. Như vậy thì chia ra 2 trường hợp. Một là SomeType phải là System.String, điều này khá khó thực hiện vì System.String không tồn tại constructor nào có tham số là chuỗi cả. Hai là SomeType.ProvideValue phải trả về một chuỗi. Sau khi xem xét toàn bộ các kế thừa của MarkupExtension thì tôi phát hiện một case khá thú vị – TypeExtension. Theo như document mặc định thì markup này chỉ trả về một Type (kiểu dữ liệu Type). Nhưng trong SharePoint thì không.
Dễ dàng thấy được hàm ProvideValue trả về một chuỗi tại dòng 36 hoặc 38. Ngoài ra lớp này còn có constructor thỏa mãn điều kiện nêu phía trên. Do đó đây có thể coi là một ứng viên tiềm năng cho công cuộc bypass.
Chúng ta sẽ phân tích kỹ hơn về cách hoạt động của hàm này. Đầu tiên là thuộc tính typeName sẽ là chuỗi bất kỳ (đã nếu phía trên). Tại dòng 23-24, chương trình sẽ kiểm tra ký tự “:” trong typeName sau đó chia thành 2 phần (tương tự như namespace trong XML). Ví dụ: typeName là “abc:SomeType” thì text2 là “abc” còn text là “SomeType”. Dòng 28 sẽ kiểm tra và lấy ra kiểu dữ liệu từ config, nếu tồn tại thì sẽ trả về kiểu dữ liệu tìm được. Nhưng cái chúng ta cần thì ngược lại, dòng 28 phải trả về null mới có khả năng bypass. Lưu ý là tất cả namespace đều được kiểm tra tính hợp lệ. Cú pháp của namespace sẽ như sau.
Để ý kỹ hơn dòng 36, hàm chỉ đơn giản là trả về namespace + “.” + text. Như vậy thử tưởng tượng rằng, nếu chúng ta có một namespace hợp lệ, ví dụ như System và text là Resources.ResourceSet. Vậy điều cần thiết ở đây là làm thế nào cho dòng 28 trả về null. Rất đơn giản, chúng ta chỉ cần để một assembly khác. Trong ví dụ này, tôi để mục assembly là System.Workflow.ComponentModel và code ở dòng 28 thực chất chỉ là tìm kiếm System.Resources.ResourceSet trong thư viện System.Workflow.ComponentModel. Điều này đương nhiên sẽ trả về null vì không tồn tại kiểu dữ liệu này, sau đó trả về chuỗi tại dòng 36. Như vậy sau một quá trình dài dòng thì chúng ta đã gán được ValueType là System.Resources.ResourceSet.
Quay trở lại với quá trình validate config, TypeExtension nằm trong allow list cho nên việc này hoàn toàn khả thi. Payload hoàn chỉnh của chúng ta sẽ là:
Ở đây tôi không dùng x:Type mà thay vào đó là ns1:TypeExtension, điều này để chắc chắn việc gọi hàm ProvideValue không bị nhảy vào markup mặc định.
Kết luận
Thật ra tôi đã phát hiện ra flow này trước khi tìm được bug ở phần 1. Nhưng nói gì thì nói, trong giang hồ vẫn tồn tại một ý niệm là có CVE thì lời nói uy tín hơn, và việc này cũng làm cho timeline hợp lý hơn vì theo đánh giá cá nhân, đây không phải là một bug “xàm”… Timeline thật sự sẽ như sau:
Tìm ra và report bug phần 2.
Microsoft chuyển qua Online Service và không đánh CVE (Trái đắng).
Cay cú, tiếp tục tìm bug để có chỗ đứng trên “đấu trường danh vọng”.
Tìm ra và report bug phần 1 (có CVE đầu tay).
Quả ngọt – Microsoft quyết định trao bounty và bonus vì những báo cáo chuẩn mực của bug phần 2 😊.
Tôi không rõ lý do việc microsoft từ chối đánh CVE cho bug này, có thể là do dup mà chưa patch hết. Nhưng dù thế nào đi nữa, con số 26 như một happy ending cho chuỗi ngày dài đau khổ cùng SharePoint.
Thời điểm giữa năm 2021, cộng đồng bảo mật xôn xao vì hàng loạt lỗ hổng 0-day của Exchange được công bố. Người người đi diff, nhà nhà có poc, chẳng cho mình là ngoại lệ, tôi cũng nhảy vào nghiên cứu phân tích cho bằng bạn bằng bè. Tuy nhiên đời không như là mơ, trong khi anh em đã ra lỗi thì bản thân lại đang quanh quẩn ở một phase nào đấy chẳng chút liên quan. Không há miệng chờ poc, tôi bắt đầu nghiêm túc với công việc gọi là research này. Chọn một mục tiêu mà bản thân nghĩ sẽ nhẹ nhàng hơn cũng như để tích lũy kinh nghiệm về .NET – SharePoint Server là ứng cử viên sáng giá.
Công cuộc tìm kiếm CVE đầu tay bắt đầu, mặc dù gặp nhiều khó khăn nhưng sau vài tháng thì tôi cũng có thành quả với lỗ hổng Store XSS (CVE-2022-24472). Vẫn chưa bằng lòng với kết quả này, suy nghĩ của tôi lúc đó là “Các thứ khác có hay không không quan trọng, CVE đầu tay phải là RCE”. Vì vậy tôi đã không để acknowledgement và tiếp tục dấn thân sâu hơn vào con đường này. Ông trời không phụ lòng người, sau hơn nhiều tháng tìm kiếm và chờ đợi, ngày 13 tháng 12 năm 2022, lỗ hổng được vá và đánh số CVE-2022-44693.
Phân tích
Đây là một biến thể của CVE-2021-34520 tồn tại trong chức năng Workflow của SharePoint. Mọi người có thể tham khảo phân tích của lỗ hổng này tại đây. Có thể tóm tắt như sau:
Workflow là một luồng công việc được thực hiện một cách tự động, bao gồm nhiều bước.
Các bước này sẽ chúa một hoặc nhiều hoạt động (activity) và các điều kiện (rule).
SharePoint có một danh sách các activity/rule được đánh dấu là an toàn (allow list) và người dùng chỉ có thể tạo workflow (dưới định dạng XML) với các activity/rule đó. Quá trình kiểm tra đều dựa trên định dạng XML.
SetVariableActivity (nằm trong danh sách an toàn) có chứa thuộc tính ValueType. Nếu thuộc tính này mang giá trị là chuỗi System.Resources.ResourceSet thì có thể dẫn đến remote code execution.
Lỗ hổng được vá bằng cách kiểm tra giá trị của các thuộc tính với allow list. Nếu thỏa mãn thì sẽ thực thi và ngược lại.
Payload sẽ như sau:
1. Payload cho CVE-2021-34520
Trong qua trình kiểm tra workflow config, hàm IsGoodWorkflowCore trong thư viện Microsoft.SharePoint.dll được gọi với xmlReader chính là file config và authorizedTypes là allow list đã quy định từ sẵn.
2. Hàm IsGoodWorkflowCore trong bản vá tháng 10 năm 2022
Đây là một hàm rất dài, chức năng của nó là đọc lấy giá trị của từng node và thuộc tính sau đó đem so sánh với allow list, cuối cùng là trả về true/false nếu thỏa mãn/không thỏa mãn. Cũng vì sự dài dòng nên tôi đã chăm chăm vào việc tìm chỗ bypass logic mà không để ý đến những dòng code cuối. Nếu quá trình đọc xảy ra bất kỳ exception nào thì chương trình sẽ ghi log lại và … return true. Do đó ta chỉ cần tìm một đoạn code có khả năng gây exception thì sẽ bypass hoàn toàn được cơ chế này. Chúng ta sẽ đi tiếp vào hàm IsAuthorizedType, hàm này được gọi bởi IsGoodWorkflowCore. Nó có tác dụng đọc và so sánh thuộc tính từ workflow config.
3: Hàm IsAuthorizedType trong bản vá tháng 10 năm 2022
Tại dòng 221, attributeValue được tách ra với ký tự “{” sau đó gán vào mảng array. Còn tại dòng 225, giá trị array[1] được gọi. Ta dễ dàng thấy rằng nếu attributeValue mang giá trị là “” hoặc “{}” thì sẽ gây ra IndexOutOfRangeException vì lúc này mảng array chỉ có duy nhất một phần tử. Tới đây mọi thứ đã quá đơn giản, mặc dù vẫn còn một số vấn đề khác để hoàn thành CVE nhưng nó không ảnh hưởng quá nhiều đến kết quả. Payload cho lỗ hổng như sau:
4. Payload cho CVE-2022-44693
Ở đây tôi chọn thuộc tính Name với giá trị là “{}” do nó là thuộc tính mặc định và có thể nhận bất cứ giá trị nào (quá trình parse XML để chạy workflow sẽ có bước kiểm tra thuộc tính có tồn tại trong acitvity/rule hay không). Bên cạnh đó, thuộc tính xmlns cũng có thể được sử dụng thay vì Name. Ngoài ra, còn rất nhiều vị trí có khả năng gây exception, ví dụ như dòng 243 trong chính hàm IsAuthorizedType.
Kết luận
Mặc dù impact là remote code execution nhưng theo đánh giá của những người anh em xã hội, đây là một bug khá dễ, khả năng bị trùng không nhỏ. Và thật sự nó đã bị “dup”, tuy nhiên đây vẫn là một thành quả không nhỏ, cũng như một bước đệm để tôi tiếp tục với bug workflow ở phần 2.
Buổi chiều thứ 3 ngày mùng 9 tháng 11 năm 2021, khi những cơn gió đầu đông ùa về, lòng tôi man mác nhớ một hình bóng đã xa. Chìm đắm trong những suy nghĩ miên man, tôi nhận được thông tin từ đồng nghiệp về một lỗ hổng thực thi mã từ xa của máy chủ Microsoft Exchange (CVE-2021-42321) sẽ được vá trong bản patch tháng này của M$. Ồ là em, exchange, người đã làm tôi mất ngủ nhiều đêm tháng 4.
Năm bảy tuổi, bắt được ve sầu, cứ tưởng có cả mùa hè trong tay. Năm 20 reproduce được cái bug, cứ ngỡ sắp đi P2O.
dieulink
Gần đây mới diễn ra cuộc thi Tianfu Cup, nghe đồn có RCE nên tôi đoán là bug này.
Do đó, chúng tôi dự định sẽ đợi đến đêm ngày thứ 3 khi Microsoft phát hành bản vá sẽ bắt tay ngay vào phân tích.
Đầu tiên tôi cài phiên bản Exchange Server 2019 CU11. Phiên bản này bao gồm tất cả các bản patch từ tháng 9 trở về trước. Vì vậy tôi chỉ cần cài thêm bản vá tháng 10, đợi M$ ra bản vá tháng 11 và diff 2 phiên bản với nhau.
Tôi sử dụng Uninstalltool để kiểm tra các file thay đổi khi cài đặt bản vá tháng 11. Số lượng file thay đổi lên đến hơn 1000 file, bao gồm gần 800 file dll, số còn lại bao gồm các file config, js và một số file data.
Tôi tập trung phân tích các file dll vì đây là các file chứa mã lệnh chạy trên server, các lỗ hổng liên quan đến server chủ yếu sẽ khai thác các đoạn code trong các file dll này. Để có thể diff các file dll thì cần phải dịch ngược chúng ra file source code và sử dụng phần mềm Winmerge so sánh với nhau. Tôi mất gần 3 tiếng để có thể export toàn bộ số dll với một server 12 core 64 GB ram.
Sau khi export xong thì tôi dùng phần mềm Winmerge để diff 2 folder chứa source code với nhau. Kết quả có quá nhiều vị trí khác nhau, nhiều vị trí gây nhiễu và tôi phải tiến hành tự chỉnh sửa, tiếp tục mất thêm một tiếng nữa. Quá trình này chủ yếu đòi hỏi sự kiên trì và thao tác tay nhanh nhẹn.
Sau khi đã loại bỏ các vị trí gây nhiễu, số vị trí có thay đổi của bản vá lần này vẫn là khá nhiều và chủ yếu liên quan đến lỗ hổng deserialize bằng BinaryFormatter. Đây là formatter có lợi cho người tấn công vì thường không có cơ chế kiểm soát type của dữ liệu được xử lý. Có thể nói Microsoft muốn xử lý toàn bộ các vị trí có nguy cơ trong bản vá lần này.
Do có quá nhiều vị trí patch nên bài toán lớn nhất để có thể reproduce được lỗ hổng này đó là tìm ra vị trí có thể trigger được và ta có thể control được input.
Hết ngày thứ 4 (10/11)
Thứ 5 (11/11), sau khi phân tích qua các vị trí thì tôi nhận thấy class TypedBinaryFormatter đã bị xóa.
Tại class này, hàm Deserialize nhận tham số binder, thường có tác dụng kiểm soát type để ngăn chặn tấn công deserialize, được truyền vào nhưng lại không được sử dụng. Thật sự tôi chỉ biết thốt lên ảo ma canada.
Tôi thử nghiệm khai thác Deser class Type sau đó test poc thì xác nhận là có thể bypass check type bằng gadget TypeConfuseDelegate.
Kể từ sau đó thì quá trình đi vào bế tắc. Chúng tôi chia nhau phân tích các hướng khác nhau, bao gồm từ RPC, Powershell nhưng không có kết quả. Đến mức tôi phát hiện ra một lỗ hổng self RCE.
Mọi thứ tưởng như đã kết thúc thì đến tối ngày 17/11, người em Q của tôi sau khi trace hết các đoạn code đã phát hiện ra một file ini bị bỏ sót.
Class ClientExtensionCollectionFormatter được thêm vào EnableStrictLocation, chính class này cũng gọi đến TypedBinaryFormatter. Từ đó, mọi người bắt đầu trace theo các class, interface có liên quan đến nó.
Sử dụng chức năng quản lý add-ins ở /ecp để có thể gọi được đến quá trình deserialize.
Dữ liệu đưa vào deserialize được lấy từ userConfiguration.GetStream()
Vậy vấn đề cần giải quyết là làm sao để có thể control được được userconfiguration.GetStream. Thực sự tôi đã xem qua đoạn này nhưng vì không thể tìm được input của dữ liệu nên đã đi theo các hướng khác.
Nếu như trace theo đúng các hàm override ở class thì dnSpy không thể tìm được caller, vì vậy phải trace theo các hàm ở interface.
Như vậy luồng xử lý liên quan đến dữ liệu từ UserConfiguration. Search trên mạng tôi tìm thấy tài liệu của Microsoft về cách thay đổi UserConfiguration thông qua service EWS.
Như vậy tôi có thể control được dữ liệu trong Stream của UserConfiguration. Tiếp theo là tìm đúng định danh của configuration được sử dụng. Các giá trị này được lấy từ hàm LoadMasterTable (đã biết trong quá trình trace).
Tiến hành set giá trị tạo từ payload ysoserial.net
Qua giám sát an toàn trên không gian mạng chúng tôi phát hiện thông tin từ một số tài khoản MXH nghi ngờ rằng công cụ được cung cấp trên trang https://tiemchungcovid19.gov.vn là một mẫu mã độc.
Tập tin trên là công cụ nhỏ được thiết kế dành riêng cho các cơ sở Y tế để chuẩn hóa dữ liệu Excel trước khi nhập vào hệ thống, vì tình trạng dữ liệu nhập vào không đồng nhất, một số địa điểm có đăng ký giấy phải nhập liệu thủ công.
Thông tin được các cá nhân này đưa ra dựa vào một số dấu hiệu sau đây:
Windows Defender cảnh báo tệp tin có chứa nội dung nghi ngờ là mã độc
Đánh giá 9/65 phần mềm duyệt virus trên thế giới nhận định công cụ này là mã độc (nguồn virustotal)
Tuy nhiên nếu chỉ dựa vào một số thông tin nêu trên để kết luận đây là một tệp tin độc hại thì hoàn toàn chưa đủ căn cứ. Chính vì thế chúng tôi đã tiến hành kiểm tra và phân tích tệp tin này để có được thông tin chính xác nhất.
Dưới đây là một số thông tin cơ bản về tệp tin ImportTool.exe
Qua kiểm tra, chúng tôi phát hiện đây là một phần mềm được lập trình bằng ngôn ngữ python và được biên dịch bằng Pyinstaller.
Trước đây, có một số mã độc cũng biên dịch từ python để thành tệp tin thực thi độc hại, dẫn tới vấn đề nhận diện nhầm của trình Antivirus, do Antivirus hoạt động phần lớn dựa trên tập luật. Vấn đề nhận diện sai khi sử dụng Pyinstaller đã được đề cập nhiều trên Github cũng như các diễn đàn về Python. Chi tiết xem tại đây.
Để khẳng định nhận định trên chúng tôi thử biên dịch 1 chương trình sample bằng Pyinstaller sau đó đưa lên Virustotal để kiểm tra thì phá hiện 8/67 vendors đánh giá đây là một tệp tin độc hại (chi tiết tại đây). Qua đó có thể thấy do cấu trúc biên dịch, đóng gói một chương trình từ python sang một tệp thực thi duy nhất có nguy cơ cao bị cảnh báo nhầm thành mã độc.
Một số tệp tin thư viện python được sử dụng trong công cụ ImportTool.exe cũng chính là nguyên nhân dẫn đến các công cụ Antivirus đưa ra nhiều hơn các cảnh báo không chính xác về công cụ này. Ví dụ như:
fields.cp37-win32.pyd
timedeltas.cp37-win32.pyd
Qua những phân tích trên có thể thấy đây hoàn toàn là do các chương trình Antivirus đã vô tình nhận diện sai công cụ này thành một mã độc, tuy nhiên để an toàn hơn cho người dùng, chúng tôi đã thực hiện phân tích sâu hơn về hành vi của chương trình, cụ thể kiểm tra một số thông tin liên quan đến kết nối internet, và một số hoạt động thay đổi thông tin dữ liệu của máy tính và đưa ra một số kết luận như sau:
Công cụ không thực hiện các kết nối độc hại trong quá trình người dùng sử dụng
Công cụ không thực hiện thay đổi hay tạo thêm các giá trị trong Registry để thực hiện các hành vi độc hại.
Công cụ không tạo ra các tệp tin nghi ngờ là mã độc nào khác.
Công cụ không thực hiện chạy các phần mềm khác.
Qua những thông tin trên chúng tôi đưa ra kết luận công cụ ImportTool.exe không phải là mã độc như thông tin được đưa ra bởi một số cá nhân trên mạng xã hội Facebook.
Chúng tôi sẽ phân tích tiếp các dữ liệu và cập nhật thêm thông tin nếu có.
Camera an ninh hiện nay là một thiết bị rất phổ biến được sử dụng ở rất nhiều nơi. Nó nắm khá nhiều thông tin nhạy cảm của người dùng. Tuy vậy chưa nhiều người quan tâm tới vấn đề bảo mật của các thiết bị này lắm. Vì vậy, nhiều kẻ xấu đã lợi dụng vấn đề này để tấn công chuộc lợi cá nhân.
Ở bài viết này chúng tôi sẽ chia sẻ về quá trình tìm hiểu, nghiên cứu về một chiếc camera không tên, là khởi đầu cho việc nghiên cứu những thiết bị khác khó hơn và nhiều người dùng hơn sau này. Thực ra chúng tôi chọn camera này vì là được một người anh cho để nghiên cứu thử chứ cũng không có ý định target nó từ đầu. Việc này đã được chúng tôi làm từ trước đây rất lâu nhưng bây giờ mới có cơ hội chia sẻ lại với các bạn. Tuy nhiên sau một thời gian ngắn nghiên cứu thì chúng tôi đã làm chết con camera này dẫn đến không thể nghiên cứu sâu hơn cũng như không thể thực hiện lại một số thao tác để có một bài viết đầy đủ và chi tiết hơn. Chính vì vậy nếu bài viết này còn nhiều thiếu xót hoặc chưa đủ hấp dẫn và chi tiết thì tôi xin phép hẹn các bạn vào một bài viết khác, về một thiết bị camera khác.
Tiếp cận thiết bị
Với một camera chưa biết gì thì chúng ta có thể tiến hành dump firmware. Nhưng việc đó cần nhiều hiểu biết về phần cứng, là một người mới bắt đầu tôi không chọn phương án này vì cần phải hàn mạch. Hướng tiếp cận đầu tiên là kết nối thiết bị vào mạng LAN rồi thực hiện dò xét các cổng mở để tìm được hướng tấn công tiếp theo.
Để thực hiện việc này, đầu tiên sau khi kết nối vào cùng mạng wifi của camera tôi dùng công cụ nmap để quét ra ip của camera, sau đó tìm hiểu các cổng mở cũng như các thông tin liên quan:
Sau bước này, ta sẽ xác định được địa chỉ ip của camera là 192.168.50.92. Các port mở là 554, 5000 thường được mở ở các thiết bị camera. Như vậy, không có các cổng thông dụng như HTTP, SSH, …
Tiếp tục thực hiện các lệnh nmap thông dụng :
Ta lại biết thêm được một thông tin thú vị về chiếc camera này. Có vẻ như nhà sản xuất là Shenzhen. Lên google search một hồi thì công ty này có các sản phẩm về camera. Lục tìm trong các sản phẩm xem có cái nào giống cái của mình đang nghiên cứu hay không.
Sau một thời gian dài kiên nhẫn, cuối cùng tôi cũng tìm được một con có vẻ khá giống loại mình đang nghiên cứu. Tìm các sản phẩm trên thị trường Việt Nam thì ra rất nhiều loại tương tự. Camera này có số hiệu YOOSEE 720P Z06H được bán với giá 350k trên shopee. Từ đây, mình cũng tìm được app điều khiển loại camera này. Đồng thời mở ra nhiều hướng để nghiên cứu thêm.
Nghiên cứu các cổng mở
Sau bước đầu tiên, tôi phát hiện được 2 cổng TCP đang mở. Các cổng này sẽ được dùng với mục đích gì?
Cổng 554 được nmap gán với nhãn rtsp (Real Time Streaming Protocol), là một giao thức thường xuyên được sử dụng trong camera. Từ đây, tôi tìm một số công cụ để xem stream với rtsp.
Công cụ đầu tiên thành công xem được video là ONVIF Device Manager. Nó thực hiện các giao tiếp với camera qua cổng 5000 để nhận biết được các thông tin thông số kĩ thuật cũng như luồng stream. Tại thời điểm này, camera đang ở trạng thái không xác định nên tôi không thể dựng lại để viết được. Tool tiếp theo dùng được là VLC media player. Chọn Open Network Stream Url. Trong trường hợp này, luồng stream video nằm tại rtsp://192.168.50.92:554/onvif1. Tiến hành bắt request để xem cách thức tool giao tiếp với camera như thế nào
Đúng như mong đợi, công cụ thực hiện các request tới camera qua cổng 554, tuân theo chuẩn của rtsp.
Sau khi nghiên cứu hai ứng dụng này, tôi chưa phát hiện ra cơ chế xác thực nào của ứng dụng. Chả lẽ chúng ta có thể xem video từ camera một cách tự do thế sao?
Nghiên cứu ứng dụng Yoosee
Camera này có một ứng dụng quản lí trên điện thoại : Yoosee. Chúng ta có thể tải ứng dụng về, tạo tài khoản, đăng nhập và thực hiện các tác vụ quản lí camera trên ứng dụng này. Ứng dụng này cho phép chúng ta thêm thiết bị qua mạng lan, nó sẽ tự động dò quét ip để tìm ra camera, tiếp đến chỉ cần nhập password của camera là có thể quản lí thiết bị. Password mặc định là 123 có vẻ như là phổ biến cho tất cả các thiết bị.
Sau khi thêm thiết bị vào, các ứng dụng tại bước hai sẽ không thể xem video được nữa. Cũng không thể có 1 lúc 2 thiết bị cùng thêm một camera. Vậy là đã có câu trả lời cho bước trước tại sao không cần xác thực mà vẫn xem được video. Tuy nhiên, bằng ứng dụng ONVIF Device Manager, chúng ta vẫn có thể thực hiện các lệnh làm cho camera chuyển động. Tức là vẫn có những hành động mà chúng ta có thể tác động lên con camera này. Điều này mở ra nhiều hi vọng tìm kiếm những chức năng tương tự.
Thực hiện bắt request trên thiết bị android :
Ứng dụng giao tiếp với camera bằng giao thức UDP thông qua cổng 51880. Hmm có vẻ như là một cổng khá lạ, quét bằng nmap không ra. Các gói tin gửi đi cũng khá là vô nghĩa, nên ta khó có thể phân tích được nhiều.
Nghiên cứu firmware
Qua các bước trên, tôi đã có được cái nhìn sơ bộ về cách hoạt động của camera. Nhưng muốn nghiên cứu sâu hơn, cần phải có được mã nguồn firmware của camera. Lượn lờ trên google một hồi tôi cũng tìm được link thú vị này.
Nó cho phép chúng ta update firmware của camera đồng thời đi kèm là các bản firmware.
That’s all we need. Tiến hành upgrade thử một firmware. Tôi lại phát hiện ra một chức năng không cần xác thực nữa. Nếu không cần xác thực thì chúng ta có thể nghĩ cách inject backdoor vào trong firmware rồi update bản cập nhật. Tuy nhiên, dù không xác thực nhưng nó vẫn check MD5 ở đâu đó, nên khả năng chúng ta có thể động vào bản update là không thể.
Đến đây, tôi lại có một câu hỏi : điều gì xảy ra nếu ta thực hiện update các phiên bản chính của nhà phát hành nhưng là phiên bản cũ hơn phiên bản hiện tại. (⊙_⊙)?Phiên bản hiện tại camera mà tôi đang nghiên cứu là 21.00.00.95. Tôi lặng lẽ thử downgrade xuống phiên bản 14.00.00.00. Và rip chiếc camera die luôn. Nhấn nút reset cũng không thấy có phản hồi gì. Rip.
Tại đây, cũng mở ra nhiều hướng tấn công khác như là tìm một phiên bản chứa lỗ hổng bảo mật đã được khai thác, rồi downgrade firmware xuống, thực hiện tấn công trên các lỗ hổng đã có.
Extract firmware
Tiếp đến, extract firmware ra và xem chương trình thực hiện những gì. Extract bằng binwalk ta thu được:
File 23B964.elf là file update firmware. Thư mục jffs2-root chứa một phần firmware update. Tôi sẽ tập trung vào thư mục jffs2-root/fs_1
Trong thư mục này có chứa các file cấu hình của camera cũng như các file .xml dùng cho Onvif. File cần lưu ý nhất là file npc. Nó là chương trình chính điều khiển camera.
Phân tích sơ bộ file npc
File này là một file khá lớn, bị stripped hết nên sẽ tốn rất nhiều thời gian để dịch ngược toàn bộ chức năng của chương trình. Như hình trên có chức năng ẩn là thực hiện mở telnetd , tuy nhiên tôi chưa tìm ra cách để kích hoạt nó. Tôi xem qua ứng dụng cũng không thấy có các chức năng để mở cổng telnetd này. Đây có vẻ là một chức năng ẩn. Câu hỏi đặt ra là chức năng này được tạo ra để làm gì \(〇_o)/
Tiếp đến, tôi sẽ tập trung để tìm các hàm mở các cổng 5000, 554, 51880. Bằng cách lần theo các hàm để tạo socket như bind, connect, recv, send, ….
UDP 8899
Trong bài viết này tôi sẽ tập trung vào tính năng unauthen upgrade đã tìm hiểu ở bước trên. Tôi đã dịch ngược được hai hàm xử lí của 2 port TCP đã mở. Và không có hàm nào xử lí việc upgrade cả. Sau khi bắt request, tôi phát hiện ra tool upgrade giao tiếp với camera qua cổng 8899.
Hàm xử lí request tới cổng 8899 nằm tại địa chỉ 0x18EE64.
Chúng ta có thể chọn các lệnh để thực hiện chức năng thông qua bytes đầu tiên của request. Tại đây, chúng ta cũng có rất nhiều các chức năng unauthen nhưng có khá nhiều tham số mà khó có thể kiểm soát. Bộ nhớ được kiểm soát cũng rất tốt nên không có hàm nào bị buffer overflow ở đây.
Tiếp đến, phân tích request mà tool upgrade gửi tới camera sẽ sử dụng option 7. Đoạn xử lý được thực thi như sau:
Từ đó, ta có thể truyền vào request để xác lập port sẽ nhận bản cập nhật từ server và phương thức upgrade. Hàm xử lí chính của upgrade nằm tại địa chỉ 0x191B70.
Tại đây, ta thấy có rất nhiều lựa chọn upgrade chứ không chỉ upgrade mỗi firmware.
Việc upgrade các file hệ thống không có check md5. Ta hoàn toàn có thể tự tạo request và khiến cho hệ thống này cập nhật những thứ không mong muốn.
Tôi đã tiến hành dựng lại để thiết lập được một tính năng tấn công làm cho camera tự khởi động lại.
Đầu tiên thực hiện gửi request tới cổng 8899 để chọn option 7 cho tính năng upgrade. Thiết lập port và option upgrade. Tôi chọn port 21200 và option 0x2.
Sau đó, tạo server bind cổng 21200 để gửi bản cập nhật. Set các tham số tương ứng để thỏa mãn điều kiện:
Nếu các điều kiện trên đều được thỏa mãn, ta sẽ đến được đoạn:
Như vậy, hàm main sẽ thoát khỏi vòng lặp trên và tiến tới thực thi switch case:
Case 8 sẽ là thực hiện upgrade firmware từ file /mnt/ramdisk/tempRsUpg.bin . Sau đó thiết bị sẽ tiến hành reboot.
Với tính năng này, chúng ta có thể thực hiện reboot liên tục thiết bị gây ra một cuộc tấn công DOS camera.
Kết luận
Qua đây ta có thể thấy chiếc camera này mở rất nhiều cổng dịch vụ khác nhau, trong đó nhiều chức năng không yêu cầu xác thực. Thậm chí nó cho phép chúng ta downgrade phiên bản, tự động reboot,…. Làm hư hại nghiệm trọng tới thiết bị. Mở ra nhiều hướng tiếp cận có thể exploit được. Tuy nhiên do việc làm die con camera quá sớm dẫn đến còn nhiều thứ chưa được thử trên con camera để confirm nên chúng tôi không chia sẻ thêm ở đây. Cuối cùng tôi hi vọng bài viết đơn giản này có thể giúp mang lại cho các bạn cái nhìn khách quan hơn về tìm kiếm lỗ hổng trên các thiết bị camera.
Dạo này thấy các Security Researcher thi nhau public các bài viết phân tích n-day khiến cộng đồng security trong nước xôn sao. Cũng vì thế mà ae trong team dù đang rất nhiều việc cũng phải tranh thủ nhảy vào nghiên cứu bú fame cho bằng anh bằng em.
Người người phân tích Pre-Auth thì chúng tôi tạm thời phân tích Post-Auth, nhà nhà phân tích Exchange thì chúng tôi phân tích Sharepoint. Chúc anh em đọc có nhiều niềm vui và có thêm thông tin hữu ích.
1. DIFF PATCH
Sau khi tải và cài đặt Sharepoint cùng với các bản patch (phiên bản Foundation 2013 SP1), ta lấy các dll tiến hành diff. Các dll của Sharepoint hầu hết ở 3 thư mục:
C:\Windows\Microsoft.NET\assembly\GAC_MSIL
C:\Program Files\Common Files\microsoft shared
C:\inetpub\wwwroot\wss
Tên file đều có pattern là Microsoft.Sharepoint.*.dll. Có thể sử dụng 7zip với option regex để gom các file này một cách nhanh chóng.
7z.exe a dll.7z -r .\Microsoft.Sharepoint.*.dll
Sử dụng DNSpy export toàn bộ các file dll của cả hai phiên bản ra code C# rồi thực hiện diff bằng WinMerge. Công cụ này có sẵn filter ASP.NET Devel giúp loại bỏ các đoạn code khác nhau không quan trọng (các dòng code bị đảo vị trí hoặc thay đổi về version, …), làm quá trình diff trở nên dễ dàng hơn.
Có thể nhận thấy hầu như code được thay đổi tại các đoạn gọi hàm Deserialize; các thuộc tính như Binder, Resolver được bổ sung giúp cho việc thực hiện deserialize trở nên an toàn hơn trước input người dùng (chỉ cho phép chuyển đổi các đối tượng với kiểu dữ liệu quy định trước).
Các formatter chính được sử dụng là BinaryFormatter và XmlSerializer. Đối với BinaryFormatter nếu không chỉ định thuộc tính Binder, ta có thể trực tiếp khai thác bằng cách đưa vào các payload độc hại. Còn XmlSerializer yêu cầu kiểu dữ liệu thích hợp, nên muốn khai thác được thì phải tùy ý điều khiển được kiểu dữ liệu đầu vào.
Ta sẽ phân tích đoạn patch về BinaryFormatter do không có yêu cầu đặc biệt về input người dùng. Theo thông tin diff bên trên, trong file Microsoft.Sharepoint.Portal.dll, BinaryFormatter được gọi ở namespace WebControls.ScorecardFilterValuesProviderCollectionProxy vì vậy ta đi sâu hơn vào class này.
2. PHÂN TÍCH
Phân tích file Microsoft.SharePoint.Portal.WebControls.dll, ta nhận thấy BinaryFormater được gọi tới trong hàm ParseQueryString(string queryString). Hàm này thực hiện urldecode đối với query string của người dùng, sau đó decode base64 và truyền vào cho câu lệnh BinaryFormatter.Deserialize() để tiến hành chuyển đổi sang đối tượng. Trong đó giá trị của queryString chính là giá trị của tham số SCFVP trên URL.
Tiếp tục trace code, ParseQueryString() được gọi từ method OnPreRender() trong class KpiDisplayFormControlBase.
KpiDisplayFormControlBase kế thừa class System.Web.UI.UserControl, do đó KpiDisplayFormControlBase có thể được gọi trực tiếp từ một web form page (file aspx) thông qua Register.
Như vậy định hình được flow như sau:
Tạo một page aspx có Register gọi đến KpiDisplayFormControlBase.
Truy cập page aspx vừa tạo với giá trị của tham số SCFVP là payload thực hiện RCE.
Để có thể tạo được một page aspx trên Sharepoint thì user phải có quyền Add and Customize Pages. Quyền này thường được bật đối với các user trong group Farm Administrators. Request dưới đây sẽ tạo một page có tên là rce.aspx với các thuộc tính:
Namespace: Micrsoft.Sharepoint.Portal.WebControls
Assembly: Thông tin file dll của namespace trên (có thể lấy bằng DNSpy)
Register: Class KpiDisplayFormControlBase.
Sử dụng công cụ ysoserial.net để tạo ra payload với gadget bất kỳ mà BinaryFormatter hỗ trợ, ở đây ta chọn TextFormattingRunProperties.
Request đến page rce.aspx vừa tạo với payload trigger RCE
Tháng 3 vừa bắt đầu, ngoài cửa sổ những cơn gió xuân vẫn còn thổi mát rượi làm lòng người man mác. Tôi vẫn nhớ đó là một sáng thứ ba đẹp trời ngày 02/03/2021, thời tiết thủ đô chợt làm tôi nghĩ tới vài câu thơ của Nguyễn Đình Thi:
Sáng chớm lạnh trong lòng Hà Nội
Những phố dài xao xác hơi may…
Thế mà bỗng dưng BÒM,4 lỗ hổng bảo mật nghiêm trọng CVE-2021-26855, CVE-2021-26857, CVE-2021-26858 và CVE-2021-27065 liên quan tới Exchange Mail Server được Microsoft công bố, làm chao đảo và dậy sóng giới an toàn thông tin. Cả thế giới cảnh báo nhau, tin tặc đua nhau tìm kiếm và khai thác, nhiều công ty, tổ chức, tập đoàn bị “dính bom”. Ở Việt Nam, NCSC cũng đã ghi nhận nhiều trường hợp các tổ chức, công ty bị tin tặc xâm nhập và khai thác hệ thống thông qua những lỗ hổng này.
Nếu đọc đến đây mà bạn vẫn chưa hiểu gì, thì nhanh chóng đọc một chút ở đây.
Tại thời điểm hiện tại, hoạt động tấn công và khai thác lỗ hổng ProxyLogon cũng đã giảm, phần lớn các hệ thống đã fix lỗi, một số khác chắc đã được các bên thứ 3 remote vào fix hộ :p
Báo viết về proxylogon, nhà nhà cảnh báo proxylogon, người người cầm exploit proxylogon, nhưng chỉ thấy lẻ tẻ vài bài về ứng cứu sự cố tấn công proxylogon. Chúng tôi cũng may mắn được giúp đỡ một số đơn vị, xử lý các sự cố liên quan đến tấn công lỗ hổng bảo mật proxylogon nên có viết một chút về quá trình này.
Điều tra, ứng cứu thì làm gì ???
Thu thập hết log, sử dụng PowerShell command được Microsoft hướng dẫn để rà soát những đoạn log khả nghi. Chúng tôi nhận thấy kẻ xấu đã do thám lỗ hổng từ ngày 28/02/2021, trước khi chi tiết về các CVE được Microsoft công bố vào ngày 02/03/2021. link
Rà soát webshell
Sau khi đã phát hiện được những dấu hiệu tấn công và khai thác ban đầu, chúng tôi tiếp tục rà soát các webshell có trong hệ thống. Sử dụng YARA rule để phát hiện những file nghi ngờ là webshell. Các file khả nghi bao gồm:
Dựa vào access log của server, chúng tôi nhận thấy có nhiều kết nối đến webshell từ ngày 04/03/2021.
Song song với quá trình sử dụng YARA rule, chúng tôi sử dụng PowerShell script được Microsoft công bố nhằm so sánh sự khác biệt giữa các file gốc của Exchange với phiên bản hiện tại để tìm ra các file không thuộc hệ thống gốc. Đoạn script có tên Compare Exchange Hashes có thể tìm thấy tại link này.
Tiếp đến, chúng tôi thực hiện rà quét những file nén dữ liệu (.7z, .zip, .rar) bất thường mà kẻ tấn công có thể sử dụng để tiếp cận và tải về mã độc. Chúng tôi phát hiện có dấu hiệu exfiltration dữ liệu vào ngày 5/3.
Rà soát mã độc
Sử dụng bộ công cụ Windows Sysinternals
Process Explorer
Autoruns
TCPView
Sử dụng DeepBlueCLI để rà soát Windows Event Logs
DeepBlueCLI là một bộ công cụ có chứa các PowerShell script giúp phát hiện những bất thường trong hệ thống dựa vào Windows Event Logs. Bằng DeepBlueCLI, chúng tôi đã phát hiện có sự kiện xoá logs vào các mốc thời gian 7/3 2:50 a.m 11 p.m và 9/3 1 a.m; 10/3 1-3 a.m.
Sử dụng ListDLLs để phát hiện các hành vi load DLL khả nghi
Sau khi rà soát bằng các công cụ trên mà không tìm thấy mã độc, chúng tôi cứ ngỡ rằng hệ thống này chưa quá “toang”.
Trực giác của chúng tôi cho thấy có một cái gì đó không đúng ở đây, có thể hacker sử dụng một kỹ thuật nào đó để ẩn đi hành vi của mã độc.
Bằng cách rà soát các DLL được load bởi tất cả các tiến trình (bằng một phiên bản customize “nội bộ NCSC” của công cụ ListDLLs), chúng tôi phát hiện ra một tiến trình load DLL không đúng với “thiết kế của nhà sản xuất”. Rà soát lại tiến trình này cũng như các DLL khả nghi, chúng tôi phát hiện một số mẫu mã độc đã sử dụng kỹ thuật DLL Side-Loading để che dấu hành vi của mình.
Tóm lại là
Phân tích mẫu mã độc
Cuối bài viết chúng tôi sẽ trình bài kết quả phân tích hành vi của một số mã độc đã thu được trong quá trình xử lý sự cố.
Microsoft.Exchage.Client.Event.dll
Miêu tả: File Dll này được viết trên nền tảng .NET, được pack bởi .NET Reactor, các tên hàm và tên biến đều ở định dạng utf-16 nên hơi khó để dịch ngược.
Vậy nên, ta có thể sử dụng de4dot để unpack .NET Reactor bằng command như sau:
Sau khi quá trình unpack kết thúc, ta sẽ nhận được một file dll có tên là Microsoft.Exchange.Clients.Event-cleaned.dll. Load file này vào dnspy thì ta có thể đọc code đơn giản hơn trước khi unpack.
Flow hành vi của file này được miêu tả cơ bản qua hình sau:
MSExchangeHM.exe
Miêu tả: file exe chạy với một tham số là đường dẫn tới file Dynamic-Link Library (DllPath) và khởi chạy dll đó bằng cách Inject vào process có commandline OWAApp như flow sau:
Kỹ thuật Inject được sử dụng trong file này có tên là Inject Dll by APC sử dụng hàm NtCreateThreadEx. Bạn có thể đọc chi tiết hơn về kỹ thuật này tại đây.
File này sẽ có chức năng để load các dll, chúng tôi tìm thấy một dll khác đáng nghi ngờ trong cùng thư mục với file exe này. File đó có tên là MSOARES.dll, khá giống với tên của một file dll của Microsoft là MSOERES.dll
File Dll này thực hiện chức năng hook vào LogonUserEx API, mỗi lần hệ thống gọi đến làm LogonUserEx thì nó sẽ ghi username và password vào file có định dạng là C:\ProgramFiles\Microsoft\ExchangeServer\V15\FIP-FS\Data\Engines\metadata\manifest.{8b26bc7d-829d-4354-9527-(time_date)}.cab
Bạn có thể đọc kỹ hơn về kỹ thuật hook được sử dụng trong file dll này tại đây.
Bài viết cũng đã dài, sẽ viết tiếp phần phân tích các mẫu còn lại trong ngày gần nhất. Đi ngủ phát mai còn đi rà soát mấy con F5 đang bị đào coin :vvvv
Lời đầu tiên chúng tôi xin thay mặt BTC gửi lời cảm ơn đến các đội đã tham gia cuộc thi. Sau một thời gian để các đội tiếp tục tự nghiên cứu và tìm hướng giải cho các thử thách còn lại nhằm nâng cao chuyên môn cũng như kỹ năng nghiệp vụ của mình, hôm nay BTC sẽ công khai lời giải cho các thử thách có ít đội giải được. Đối với các thử thách có nhiều đội giải được các đội nếu có thắc mắc có thể liên hệ BTC thông qua kênh hỗ trợ Discord để được giúp đỡ.
Backdoor Detected (Category: Webshell)
Ở thử thách này BTC gửi cho các đội một tập tin bao gồm một file access_log.txt và một thư mục ROOT của web server. Sau khi xem qua các tệp do BTC cung cấp, tệp tin đáng ngờ nhất là tệp tin banking.jsp.
Step 1: Decode UTF-16
Thoạt nhìn qua chúng ta có thể phát hiện đây là một kiểu encode dạng UTF-16, chính vì thế chúng ta cần thực hiện decode UTF-16 để có thể có được một nội dung có thể đọc được.
Sau khi thực hiện decode UTF-16 chúng ta có thể thấy đây là một đoạn mã java, đoạn mã này tiếp tục bị làm rối bằng cách thay đổi tên các biến có khuôn dạng $_$ làm cho việc đọc hiểu đoạn mã trở nên khó khăn hơn.
Step 2: Khôi phục các hàm, biến và string của webshell
Sau khi có được tên webshell là banking.jsp, kết hợp với access_log.txt, bước đầu có thể thấy payload được gửi đến webshell qua POST method và ta hoàn toàn không có trong tay payload, vì vậy flag có thể được giấu đâu đó trong webshell hoặc ở một trong số các tệp còn lại của thư mục ROOT. Để biết được nội dung bị đánh cắp được lưu ở đâu chúng ta cần phải hiểu được cách thức hoạt động của web shell này.
Sử dụng một trang web https://kt.gy để khôi phục nhanh các chuỗi byte thành các string
Dựa vào các string, class và function mặc định được sử dụng ta có thể dự đoán được chức năng chính của các hàm được sử dụng trong web shell
Step 3: Khôi phục hàm main của webshell
Thực hiện tương tự như các bước ở step 2 chúng ta có được nội dung hàm main như sau
Có thể viết một cách ngắn gọn lại như sau
Có thể nhận thấy sau khi payload được gửi lên, web shell thực hiện giải mã chúng, sau đó thực thi, mã hóa output và ghi vào file 404.html.
Như đã trình bày ở step 2, ta không có payload của kẻ tấn công và payload của kẻ tấn công gửi lên sẽ được giải mã và thực thi trong hàm Exec_Command. Do đó, ta chỉ tiến hành dịch ngược đầy đủ hàm Base64_Encode_AES_Encrypt để có các giá trị, từ đó khôi phục data được giấu trong tệp “404.html”
Step 4: Dịch ngược Base64_Encode_AES_Encrypt và giải mã dữ liệu
Sau khi trích xuất được dữ liệu được mã hóa và giấu trong file 404.html, chúng ta cần phải nắm được cách thức mà web shell đã thực hiện mã hóa chúng mà không cần quan tâm payload kẻ tấn công gửi lên được giải mã như thế nào. Chính vì thế chúng ta chỉ cần tập trung vào đọc hiểu hàm Base64_Encode_AES_Encrypt.
Lời gọi hàm Base64_Encode_AES_Encrypt dạng ngắn gọn
Dựa vào string, class và tham số, biến được sử dụng trong các hàm, ta dịch ngược hàm Base64_Encode_AES_Encrypt.
Có thể thấy các tham số được truyền vào hàm Base64_Encode_AES_Encrypt bao gồm như sau:
Param1 = “99wp_B0uNTy_p1Z!”
Param2 = “192.168.18.80” chính là remote addr, ip nguồn của kẻ tấn công được lấy từ file access_log.txt
data = nội dung được đưa vào để mã hóa
Step 5: Thực hiện giải mã bằng cách viết lại script bằng python.
Zerologon 01 (Category: Zerologon)
Lỗ hổng Zerologon được khai thác theo flow sau:
Vì là zerologon nên ta sẽ tập trung vào giao thức RPC_NETLOGON (trong file pcap được ký hiệu là những packets màu tím). Trong zerologon, có hai lần chúng ta xác thực thành công Netlogon, một là khi ta muốn set password về rỗng, và 2 là khi chúng ta muốn set lại về password ban đầu.
Step 1: Set empty password
Forensic một lượt từ đầu file pcap, tại packet 3816, đây là lần xác thực thành công đầu tiên dựa vào status của packet:
Trong packet 3818, là request NetrServerPasswordSet2 -> set password về rỗng. Malformed Packet (được chú thích bên trong packet này) nghĩa là wireshark không thể phân tích nội dung gói tin thêm nữa.
Step 2: Secrets dump
Và một đoạn các gói tin tiếp là là quá trình dump data. Flag cần tìm là NTLM hash ban đầu của account computer, đó cũng là data được lấy ra trong quá trình dump. Tuy nhiên việc đọc được NTLM hash từ dữ liệu thu được trong quá trình dump là không khả thi. Chính vì thế ta cần quan tâm đến bước tiếp theo là bước reinstall password của account computer.
Step 3: Reinstall original password
Tiếp theo là quá trình reinstall original password, mà NTLM hash của nó chính là cái chúng ta cần tìm. Theo dõi tiếp file pcap, sau đoạn dump data và tập trung vào những packet màu tím, ta tìm được đoạn xác thực thành công số hai như hình dưới:
Sau khi xác thực thành công, ta thấy packet NetServerPasswordSet request được sử dụng để cài đặt lại mật khẩu của account computer. Đi sâu vào packet này, ta thấy được Encrypted NTLM Pwd:
NTLM dùng thuật toán mã hóa SAM, để decrypt thì chúng ta còn cần thêm key, trong trường hợp này, chính là sessionkey, mà sessionkey được tính toán dựa trên giá trị của server challenge:
Tại packet 8787, là response từ server, ta thấy giá trị của server challenge:
Sau khi đã có đủ ciphertext + key, ta dùng thư viện có sẵn của python để decrypt và tìm được flag:
Zerologon02 (Category: Zerologon)
Step 1: Tìm kiếm câu lệnh được thực thi
Trong quá trình phân tích file pcap, chúng tôi phát hiện có một số packet chứa nhiều chuỗi được mã hóa base64. Nghi ngờ kẻ tấn công sử dụng script powershell được mã hóa base64 tấn công máy chủ AD. Chúng tôi thực hiện search chuỗi string “powershell -e” hoặc “powershell.exe -e”
Thực hiện bóc tách script powershell từ pcap cần lưu ý rằng data bắt đầu từ sau cặp từ “IO” do script được chia thành nhiều packet để gửi đi.
Step 2: Dịch ngược script tấn công
Thực hiện decode base64 ta được một đoạn mã powershell được encrypt bằng SecureString
Thực hiện decrypt đoạn powershell ở trên bằng script sau:
Sau khi decrypt ta được một đoạn mã powershell như sau:
Các số ở đầu là index của các string phía sau, chúng ta có thể viết script python convert về script ps ban đầu, hoặc dùng tool PSDecode. Từ đó ta có được đoạn mã powershell như sau:
Với k là mảng đã biết, host name trong file pcap đã có là ABC-DC-01, dùng xor để tìm được nội dung đã ghi vào file và đó chính là flag.
Malware 02 (Category: Malware)
Step 1: Phân tích
Phân tích file mã độc được BTC cung cấp ta thấy mã độc thực hiện các hành động sau:
decrypt resource để có được shellcode
copy file mã độc vào %APPDATA%
Ghi key auto run vào registry
Thực hiện injection shellcode vào một process phù hợp bất kỳ được tìm thấy
Có thể thấy file mã độc này đơn giản chỉ inject shellcode vào một process khác trong máy tính và thực thi. Chính vì thế ta cần tập trung phân tích shellcode này.
Có nhiều hướng để có thể phân tích shellcode trong mã độc này, tuy nhiên chúng tôi sử dụng cách làm tương tự như các bước được mô tả trong bài viết tại đường [link] đối với thử thách này ta có được file dll
Step 2: Phân tích file dll được dumpra
Bắt đầu với DllMain:
Trong hàm init có define một số chức năng và mã ảnh hưởng đến thuật toán dùng để encrypt dữ liệu của các hàm như sau:
RemoteCommand => cmd = 3
Upload => cmd = 5
Keylogger => cmd = 2
Download => cmd = 4
Screenshot => cmd = 1
IP và port mà file kết nối đến:
Parse chức năng của các hàm trên
RemoteCommand
Trong hàm Command, ta thấy một số chức năng mà file thực thi.
Download
Dữ liệu từ file được đọc, sau đó đi qua hàm nén dữ liệu (rice compress) và được mã hóa trong hàm flow_send_data trước khi gửi về c&c.
Screenshot
Tương tự như hàm Download, dữ liệu từ ảnh chụp màn hình được nén lại qua hàm rice compress -> mã hóa -> gửi về c&c. Mà yêu cầu của challenge này chính là một trong số các ảnh chụp được gửi đi.
Flow send data
Hàm này được sử dụng để xử lý và gửi dữ liệu đến c&c
Sau khi phân tích ta thấy hàm này dựa trên giá trị của cmd để random một số thuật toán mã hóa, và tạo 16 bytes key random. Trong hàm algorithm, chúng ta thấy có 4 thuật toán được sử dụng ở đây
Vì ScreenShot có cmd = 1 thuật toán mã hóa sẽ là random XOR, RC4 hoặc là AES. Cuối cùng, quá trình encrypt dữ liệu screenshot giống như sơ đồ sau:
Step 3: Lấy dữ liệu ảnh chụp màn hình
Đầu tiên, chúng ta cần extract data từ file pcap bằng cách filter theo ip và port, sau đó viết script đơn giản để lấy từng packet send and recv.
Sau khi đã có các packet, phân tích theo struct của các packet như sau (trừ serpent encrypt):
4 bytes đầu là signature
4 bytes sau là insize
1 bytes tiếp là định danh hàm encrypt (0 => XOR, 1 => RC4, 2 =>AES)
16 bytes kế tiếp là key
4 bytes tiếp là outsize
Và còn lại là data
Viết script đơn giản để decrypt 3 thuật toán mã hóa trên (trừ serpent):
Tiếp đó là decompress rice. Dùng hàm decompress tương tự như [link] để giải nén dữ liệu và ta thu được một số ảnh màn hình gửi đi trong đó có một ảnh có chứa flag.
Malware 03 (Category: Malware)
Từ dữ liệu lấy được ở thử thách Malware 02 ta nhận thấy có một số packet sử dụng serpent là thuật toán mã hóa sẽ được parse theo struct sau:
4 bytes đầu là signature
4 bytes sau là insize
1 bytes tiếp là định danh hàm encrypt bằng serpent với giá trị mặc định bằng 4
Và còn lại là data
Step 1: Decrypt serpent
Sử dụng hàm như trong [link] để decrypt dữ liệu extract được từ file pcap được mã hóa bằng serpent.
Tại packet số 30, sau khi decrypt ta nhận thấy có signature của file zip và một file có tên là flag2.txt trong đó và một số file Keylogger khác.
Thực hiện giải nén file zip này ta thấy có yêu cầu nhập password. Khả năng password được lưu ở đâu đó trong những file Keylogger.
Có thể thấy trong các file Keylogger có một nội dung dạng “Th1s_1s_p4ssw0rd” tuy nhiên đây không phải là password cần tìm vì sử dựng để giải nén không thành công.
Quay trở lại đọc kỹ hơn hàm Keylogger trong file dll, ta thấy có một hàm hook_keylogger, chính hàm này đã thay đổi giá trị của phím được nhấn trước khi ghi vào file.
Theo như trên, nó đã thay đổi i -> 1, a -> 4, o -> 0. Từ đó ta có thể suy ra được password chính xác sẽ là: This_is_password
Thực hiện extract zip file với password có được ta có flag như sau:
Ở bài viết này tôi sẽ giới thiệu một cách tiếp cận để tìm các lỗi trên các ứng dụng closed-source bằng phương pháp fuzzing. VMware Workstation là một ví dụ thú vị để làm, hãy đi qua từng phần tôi trình bày dưới đây để hiểu rõ hơn về target này.
Giới thiệu
Enhanced metafile format (EMF) là một định dạng tập tin có thể chứa nhiều dạng dữ liệu được lưu dưới dạng bản ghi (record), thông thường bao gồm định dạng tập tin đồ họa như bitmap, ảnh, text,…
Enhanced metafile spool format (EMFSPOOL) là một định dạng tệp được sử dụng để lưu trữ, định nghĩa của các lệnh in (print jobs), xuất ra hình ảnh đồ họa. Các tệp EMFSPOOL chứa một chuỗi các bản ghi (record) để xử lý và phân tích cú pháp, chạy lệnh in (print jobs) trên bất kỳ thiết bị đầu ra nào.
Để hiểu rõ về định dạng này, có thể tham khảo thêm document của Microsoft [1][2] cũng như whitepaper của j00ru [3]. EMF được sử dụng rất nhiều trong print spooling, với việc sử dụng trong print spooling dẫn đến có nhiều attack surface vì nó có nhiều bản ghi với mỗi bản ghi lại lưu trữ các định dạng file khác nhau như là ảnh, phông chữ,…
Trong quá trình tìm hiểu tôi nhận thấy EMFSPOOL được sử dụng trong VMware Workstation.
Virtual Printer
Đây là một tính năng cho phép guest machine in tài liệu bằng các máy in có sẵn trên host machine (về cơ bản là chia sẻ máy in). Với cách gửi dữ liệu từ guest machine ra host machine, có thể xem đây là một hướng tấn công VM escape.
Trong quá khứ, vào năm 2015 một loạt lỗi được Kostya Kortchinsky report trong đó có lỗi stack overflow và anh ấy đã viết được exploit khai thác thành công [4]. Đến năm 2016, j00ru sử dụng fuzzing tìm ra thêm một loạt các lỗi liên quan đến các định dạng ảnh JPEG2000, EMF, phông chữ,… [5][6]
Virtual printer không được bật mặc định trên VMware Workstation. Để bật nó ta cần cài đặt như sau:
Về kiến trúc cũng như cách hoạt động, hướng tấn công của Virtual Printer, ta quan sát hình dưới:
Process chúng ta sẽ chú ý đến là vprintproxy.exe được khởi chạy trên host machine. Dữ liệu được gửi từ guest machine đến process vprintproxy.exe ở host machine thông qua cổng COM1. Dữ liệu được gửi đi từ guest machine không yêu cầu bất cứ quyền gì, tức là bất kì user nào cũng có thể tiến hành gửi dữ liệu đi. Dữ liệu gửi ra host machine được định nghĩa theo định dạng EMFSPOOL. Với định dạng này nó cho phép ta có thể đính kèm thêm các định dạng file khác qua các bản ghi như ảnh, EMF, phông chữ,…
TPView
Bằng việc tận dụng lại các POC do j00ru public từ trước [5] [6], tôi đã chỉnh sửa một chút cho phù hợp với phiên bản hiện tại, sử dụng nó và debug luồng xử lý data của Virtual Printer, tôi nhận thấy:
Quá trình xử lý data đều nằm trong TPView.dll.
Dữ liệu truyền từ guest machine đến host machine chỉ được xử lý tại TPView.dll, trên đường truyền dữ liệu không bị đối tượng nào tác động để thay đổi.
Khi phân tích file TPView.dll, tôi nhận thấy Virtual Printer xử lý một số bản ghi đính kèm các định dạng file như: JPEG2000, OTF, TTF,…
JPEG2000
Đối với bản ghi jpeg2000, đây là một bản ghi được TPView.dll custom thêm vào EMFSPOOL. j00ru phát hiện thư viện xử lý ảnh jpeg2000 của Irfanview và TPView.dll có chung code base đối với phần xử lý ảnh. Anh ấy đã port thư viện của Irfanview sang Linux và tiến hành fuzz trên đó. Đó là một cách làm thông minh và cải thiện được khá nhiều hiệu năng, tuy nhiên tôi không có kinh nghiệm port DLL sang Linux (mặc dù có khá nhiều ví dụ như LoadLibrary của Taviso [7] hay harness fuzz TTF của j00ru [8]), do vậy tôi quyết định reverse và xây dựng harness xoay quanh TPView.dll.
Trong quá trình reverse và debug tôi nhận thấy hàm sub_100584CE sẽ tiến hành xử lý bản ghi JPEG2000 gửi từ guest machine ra. Trong hàm sub_100584CE, chương trình tiến hành decompress dữ liệu và kiểm tra các trường trong data đó (do dữ liệu truyền từ guest machine ra đã được compress bằng zlib).
Kết quả của hàm decompress_data như sau:
Tôi kiểm tra hàm check_record (hàm này sẽ kiểm tra các trường có trong dữ liệu sau khi decompress) và tạo lại được struct record_jp2k và struct header chứa các trường như sau:
Khi decompress data và kiểm tra thỏa mãn các trường xong, chương trình tiếp tục tính toán và lấy các giá trị trong struct record_jp2k và header để xử lý hình ảnh jpeg2000.
Ta quan sát thấy từ dòng 84 đến dòng 110 chương trình tiến hành tạo một record_jp2k v20 để lưu trữ dữ liệu jpeg2000 sau khi xử lý. Và hàm process_jp2k chính là hàm parsing/process hình ảnh jpeg2000.
Ta thấy hàm process_jp2k nhận vào các tham số sau:
header->data_jp2k: dữ liệu file ảnh jpeg2000
header: struct header
record->len_jp2k: kích thước file jpeg2000
v20->hdr: struct lưu dữ liệu sau khi xử lý của ảnh jpeg2000
val_0x328: kích thước của record output với giá trị 0x328
Từ những tham số truyền vào ở trên thì hàm process_jp2k hoàn toàn có thể sử dụng để làm entry point của harness. Vì dữ liệu đưa vào rất sát với dữ liệu của hình ảnh jpeg2000 và nó không phụ thuộc nhiều vào các giá trị khác trong EMFSPOOL.
Dưới đây là harness mà tôi xây dựng để fuzz EMFSPOOL với record chứa file ảnh jpeg2000.
Phông chữ
Đối với bản ghi phông chữ, TPView.dll hỗ trợ xử lý đầy đủ 5 bản ghi mà EMFSPOOL có sẵn:
EMRI_ENGINE_FONT: Định dạng TTF
EMRI_TYPE1_FONT: Định dạng OTF
EMRI_DESIGNVECTOR: Chứa font’s design vector
EMRI_SUBSET_FONT: Chứa một phần phông chữ ở định dạng TTF
EMRI_DELTA_FONT: Chứa các glyph được merged với dữ liệu từ bản ghi EMRI_SUBSET_FONT trước đó.
Với mỗi bản ghi, TPView.dll sẽ có những phần xử lý khác nhau. Tuy nhiên ta có thể tham khảo tại document của Microsoft [2] về các bản ghi này.
Sau khi debug luồng xử lý bản ghi phông chữ của TPView, tôi thấy hàm sub_10013B06 sẽ là hàm tiếp nhận dữ liệu phông chữ gửi từ guest machine ra và xử lý.
Hàm này nhận vào 5 tham số tương ứng như sau:
flag là tham số đầu tiên của hàm, với mỗi giá trị flag sẽ biểu thị cho một bản ghi cụ thể:
Các tham số còn lại tôi kiểm tra qua WinDbg, đặt breakpoint tại hàm sub_10013B06. Tham số thứ 2 ở đây nó chứa một vùng nhớ kích thước 12 byte và được khởi tạo bằng NULL. Ta có thể kiểm tra giá trị đó qua hình dưới đây, ecx lưu trữ tham số thứ 2:
3 tham số còn lại như hình dưới ta có thể thấy vùng nhớ có địa chỉ 0x4453DD8 (argv3) là vùng nhớ chứa bản ghi phông chữ, tham số thứ 4 (argv4) là kích thước của vùng nhớ này. Tham số thứ 5 (argv5) có kích thước 1 byte và tôi kiểm tra qua một số lần chạy thì giá trị này luôn luôn là 1.
Đối với tham số thứ 3 (argv3) là dữ liệu bản ghi của phông chữ, với mỗi bản ghi chúng ta có một cấu trúc khác nhau nhưng ta hoàn toàn có thể tham khảo document của Microsoft [9] để hiểu thêm về cấu trúc đó. Tôi lấy ví dụ về bản ghi EMRI_ENGINE_FONT, hình dưới đây mô tả về các trường trong cấu trúc của một bản ghi EMRI_ENGINE_FONT. Tuy nhiên trong trường hợp này bản ghi lưu trong tham số thứ 3 sẽ bắt đầu từ trường Type1ID trở đi.
Ý nghĩa của từng trường trong hình ảnh trên như sau:
Type1ID (4 bytes): Một giá trị số nguyên (không âm) 32-bit, giá trị này phải là 0x00000000 để biểu thị cho định dạng TTF.
NumFiles (4 bytes): Một giá trị số nguyên (không âm) 32-bit biểu thị số lượng file TTF được đính kèm trong bản ghi.
FileSizes (variable): một mảng chứa kích thước của các file TTF đính kèm trong bản ghi.
AlignBuffer (variable): padding buffer.
FileContent (variable): chứa data của file TTF.
Ở trong trường hợp này của tôi, tôi sẽ chỉ sử dụng một file TTF cho mỗi bản ghi do đó struct EMRI_ENGINE_FONT tôi khởi tạo như sau:
Kiểm tra tham số này trên WinDbg ta có tương ứng các trường như sau:
Đến đây ta hoàn toàn có thể xây dựng harness để fuzz bản ghi phông chữ này bằng việc gọi hàm sub_10013B06. Dựa vào những thông tin tôi có ở trên thì dưới đây là mẫu harness fuzz bản ghi EMRI_ENGINE_FONT, các bản ghi khác đều tương tự chỉ thay đổi tham số flag.
EMF
Ngoài các bản ghi về JPEG2000 và phông chữ thì TPView.dll còn handle bản ghi EMRI_METAFILE, là định dạng file EMF. Sau khi nhận data từ guest machine, TPView.dll tiến hành xử lý phần data chứa nội dung file EMF. Cơ bản nó ghi data đó ra thư mục %temp% dưới dạng file và gọi các API của Windows để xử lý file này như: GetEnhMetaFileW, EnumEnhMetaFile, PlayEnhMetaFileRecord, GetEnhMetaFileHeader, GetEnhMetaFilePaletteEntries, …
Tuy nhiên ta sẽ chú ý đến API EnumEnhMetaFile:
Đây là hàm thường được sử dụng để xử lý các bản ghi chứa trong file EMF qua 1 callback là tham số thứ 3 của API này. API này được sử dụng ở trong hàm sub_10042BB6 của thư viện TPView.dll. Sau khi xem xét callback này, tôi quyết định chọn callback này sẽ là hàm mà tôi muốn fuzz vì hàm này chính là hàm sẽ parsing/process các bản ghi trong file EMF.
Như hình trên ta có thể thấy callback là hàm proc, tuy nhiên cái khó ở đây là xác định tham số thứ 4 tức là param cho hàm callback. Do param là biến cục bộ dẫn đến việc trace ngược lại để recover lại được các trường trong param cũng mất nhiều thời gian. Gặp những trường hợp như thế này tôi thường phải reverse static một chút và đoán dần các trường rồi debug để kiểm tra nó.
Đầu tiên ta hãy đoán kích thước của nó.
Quan sát các biến cục bộ được định nghĩa trong hàm sub_10042BB6, ta để ý thấy biến param bắt đầu từ ebp+0h theo sau đó là một loạt các biến khác. API EnumEnhMetaFile được sử dụng trong hàm có tham số thứ 5 là một struct RECT, tương ứng trong hàm sub_10042BB6 là biến xDest nằm ở vị trí ebp+98h. Qua đó, tôi nhận ra rằng kích thước của biến param lớn nhất chỉ có thể là 0x98 vì nếu vượt quá kích thước này nó sẽ đè lên biến xDest (tham số thứ 5 của API EnumEnhMetaFile).
Tôi tạo một struct param_s với kích thước 0x98 bao gồm các trường như trên. Tiếp tục recover các trường còn lại trong struct.
Dòng 75 và 101 trong hàm sub_10042BB6 tiến hành khởi tạo biến v23 và v26 bằng API GetDeviceCaps. Dẫn đến ta có 2 giá trị v23 và v26 trong struct là một giá trị int devcap.
Tiếp đến ta đặc biệt chú ý 4 trường v13, v21, v24, v25 trong struct, tôi sẽ đi lần lượt qua 4 trường này:
1. Trường v13
Trường này được truyền vào hàm sub_100A68D2:
Đây là hàm khởi tạo struct CStringList, nhìn vào hàm sub_100A68D2 tôi tạo lại struct CStringList như sau:
Struct CStringList này có kích thước 0x1C nên các trường từ v13 -> v19 trong struct param_s là vùng nhớ của struct CStringList. Lúc này struct param_s của tôi được cập nhật như sau:
Ta có thể khởi tạo CStringList bằng code như sau:
2. Trường v21
Không giống như trường v13, trường v21 được gán từ một vùng nhớ khác thông qua hàm sub_1000F8EA. Hàm sub_1000F8EA tiến hành gán một vùng nhớ có kích thước 8 bytes:
Do đó hàm này sẽ khởi tạo 2 trường liên tiếp trong struct param là v21 và v22. Vì được gán giá trị từ một vùng nhớ khác nên khi làm tĩnh rất khó để thấy. Tôi debug, đặt breakpoint trước khi gọi API EnumEnhMetaFile.
Ta thấy struct param_s nằm ở địa chỉ 0x4AAF57C, các trường v21 và v22 lần lượt có kích thước là 0x1c và 0x10 nằm ở các địa chỉ như trong hình. Kiểm tra các địa chỉ tpview!TPRenderW+221858 và tpview!TPRenderW+2228cc tôi biết được hàm sub_1001B7FD sẽ khởi tạo vùng nhớ gán vào v21 và hàm sub_1004559D sẽ khởi tạo vùng nhớ vào trường v22. Tôi reverse 2 hàm sub_1001B7FD và sub_1004559D để xác định các trường của các vùng nhớ mà nó tạo. Qua đó struct param_s của tôi update như sau: (các tên của struct tôi đặt theo ý thích và thói quen)
Tôi khởi tạo các struct ở trên như sau:
3. Trường v24, v25
Tương tự như v21, tôi cũng debug và kiểm tra các hàm khởi tạo 2 trường v24 và v25, kết quả tôi có được như sau:
Và đây là code tôi dùng để khởi tạo hai trường v24 và v25:
Đến đây tôi đã có thể tạo được param cho API EnumEnhMetaFile, đơn giản là chỉ cần gọi API EnumEnhMetaFile và truyền tham số vào để hoàn thiện harness:
Đến đây tôi đã trình bày cách tạo ra các harness để fuzz thư viện TPView.dll. Với những harness tôi giới thiệu ở trên, đều có thể áp dụng Winafl, pe-afl hay Tiny-afl (tool tôi xây dựng dựa trên TinyInst) để fuzz. Corpus để fuzz đối với định dạng jpeg2000, phông chữ có rất nhiều trên internet (có thể tham khảo tool tạo phông chữ của j00ru [10] [11]). Đối với file EMF tôi tạo một chương trình generate các file EMF chứa một số bản ghi phổ biến như bitmap, control, comment, clipping,…
Kết quả
Bản ghi JPEG2000: 1 bug memory corruption.
Bản ghi Phông chữ: 2 bug integer overflow (CVE-2020-3989 và CVE-2020-3990)
Ngoài ra trong quá trình reverse tôi phát hiện 1 số issues không phải security bug như:
Sai luồng xử lý của bản ghi EMRI_TYPE1_FONT do kiểm tra các trường không chính xác
Chạm int 3 do xử lý ngoại lệ khi parsing file EMF không tốt dẫn đến DOS.
Kết luận
Qua bài viết này tôi đã giới thiệu cho mọi người một cách tiếp cận xây dựng harness để fuzz ứng dụng closed-source. Đây là một ví dụ tốt để những người mới bắt đầu học fuzz có thể tham khảo. Những thứ tôi nêu ra chỉ là 1 phần xử lý của TPView.dll mà tôi biết được. Code base của TPView.dll rất lớn, tôi vẫn chưa hiểu hết được, có thể còn một số attack surface mà tôi không biết (có thể vẫn còn nhiều bug, mọi người có thể thử). Hi vọng qua bài viết này có thể giúp mọi người tiếp cận fuzzing dễ dàng hơn và sớm có bug.