19/09/2018, 20:58

Sử dụng OllyDbg trong phân tích mã độc Phần 2

Mời bạn đọc tiếp tục đến với bài phân tích Sử dụng OllyDbg trong phân tích mã độc Phần 2 của chuyên gia an ninh mạng Nguyễn Việt Anh. Đọc lại: Sử dụng OllyDbg trong phân tích mã độc Phần 1 Quan sát Thread và Stack Mã độc thường sử dụng cơ chế đa luồng. Ta có thể quan sát ...

Mời bạn đọc tiếp tục đến với bài phân tích Sử dụng OllyDbg trong phân tích mã độc Phần 2 của chuyên gia an ninh mạng Nguyễn Việt Anh.

Đọc lại: Sử dụng OllyDbg trong phân tích mã độc Phần 1

Quan sát Thread và Stack

Mã độc thường sử dụng cơ chế đa luồng. Ta có thể quan sát các thread hiện tại của một chương trình bằng OllDbg, chọn View > Threads để mở cửa sổ Threads. Cửa sổ này hiển thị vùng nhớ của từng thread và trạng thái của chúng (active, paused, suspended).

Vì OllyDbg là chương trình đơn luồng, ta có thể phải dừng mọi thread của chương trình lại, đặt một breakpoint và tiếp tục cho chạy chương trình để debug trên một thread. Click nút pause trên toolbar để dừng mọi thread. Hình bên dưới minh họa cửa sổ Thread với 2 thread vừa được dừng.

Ta có thể kill một thread bằng cách click phải vào thread và chọn Kill thread.

Mỗi thread được cấp phát riêng một stack, stack thường lưu trữ các dữ liệu quan trọng. Ta có thể dùng Memory map để theo dõi các stack trong bộ nhớ. Trong ví dụ dưới, OllyDbg đã gán nhãn stack của main thread là “stack of main thread”.

ảnh 14

Thực thi code

ảnh 16

tham khảo thêm: http://www.ollydbg.de/quickst.htm

Lựa chọn thực thi đơn giản nhất là Run và Pause. Nhưng ta ít khi dùng Pause vì chương trình có thể dừng tại vị trí không mấy hữu ích, chẳng hạn dừng tại code của một thư viện hệ thống. Thay vì dùng Pause, ta nên đặt một breakpoint kết hợp với các lựa chọn khác.

Tùy chọn Run thường xuyên được dùng khi cần restart một process bị dừng, sau khi chương trình gặp breakpoint để tiếp tục thực thi. Tùy chọn Run to Selection cho phép thực thi code đến ngay trước lệnh được chọn.

Tùy chọn Execute till Return sẽ dừng thực thi ngay trước khi hàm hiện tại thực hiện trả về. Tùy chọn này hữu ích khi ta muốn một chương trình dừng ngay sau khi hàm hiện tại vừa hoàn thành thực thi. Tuy nhiên, nếu hàm này không bao giờ trả về thì chương trình sẽ chạy vô tận.

Tùy chọn Execute till User Code hữu ích khi ta lúng túng trong code của thư viện. Khi đang dừng giữa code của thư viện, chọn Execute till User Code cho phép chương trình chạy tới khi trở về code được biên dịch (.text section).

OllyDbg cung cấp nhiều tùy chọn nhảy (step) trong các đoạn code. Stepping cho phép thực thi từng lệnh một và dừng ngay sau khi lệnh hoàn tất, cho phép ta theo dõi chương trình thực thi từng bước một. Để thực hiện single-step, nhấn F7; để thực hiện step-over, nhấn F8.

Single-step là cách dễ nhất để thực hiện nhảy và OllyDbg sẽ thực thi từng lệnh một mà không cần quan tâm lệnh đó thuộc dạng lệnh nào. Ví dụ, nếu single-step trên lệnh call sub_01007568, OllyDbg sẽ dừng tại địa chỉ 0x01007568.

ảnh 17

Nếu thực hiện step-over đối với đoạn code trên, OllyDbg sẽ dừng thực thi tại 0x010073A9. Trong trường hợp này, step-over hữu ích hơn single-step nếu ta không quan tâm hàm sub_01007568 thực hiện cụ thể những gì.

Mặc dù step-over có vẻ đơn giản, nhưng cơ chế bên trong nó phức tạp hơn single-step. OllDbg đặt một breakpoint tại 0x010073A9, tiếp tục thực thi khi ta click nút Run và khi hàm sub_01007568 vừa thực thi xong lệnh ret, chương trình sẽ dừng lại tại 0x010073A9, do nó đã gặp breakpoint ẩn mà OllyDbg tự đặt.

Trong hầu hết trường hợp, step-over sẽ hoạt động đúng như mong đợi của chuyên gia phân tích mã độc. Nhưng trong một số trường hợp, tuy hiếm gặp, mã độc có thể lợi dụng chính cơ chế step-over để làm khó người phân tích. Ví dụ, hàm sub_01007568 có thể không bao giờ thực thi lệnh ret, hoặc nó có thể thực hiện get-EIP, pop địa chỉ trả về ra khỏi stack. Khi đó, step-over có thể khiến chương trình thực thi liên tục mà không dừng lại được tại breakpoint ẩn mà OllyDbg tự động đặt.

Các Breakpoint

Có nhiều loại breakpoint và OllyDbg hỗ trợ tất cả chúng. Mặc định, OllyDbg sử dụng các breakpoint mềm, nhưng nếu cần thì ta vẫn có thể đặt các breakpoint cứng. Ngoài ra, ta có thể đặt breakpoint có điều kiện hoặc breakpoint trên bộ nhớ.

Ta có thể thêm hoặc xóa bớt một breakpoint bằng cách chọn lệnh mà ta muốn đặt breakpoint tại đó, trong cửa sổ dịch ngược, và nhấn F2. Ta có thể theo dõi các active breakpoint trên chương trình bằng cách chọn View > Breakpoints hoặc click biểu tượng chữ B trên toolbar.

Sau khi kết thúc thực thi hoặc đóng chương trình được debug, OllDbg cho phép lưu vị trí các breakpoint phục vụ cho debug lần sau.

ảnh 18

Breakpoint mềm

Các breakpoint mềm sẽ hữu ích khi debug một hàm giải mã string. Ta có thể phát hiện hành vi của mã độc thông qua các chuỗi string mà nó sử dụng, vì lí do này mà tác giã mã độc thường làm rối chúng thông qua các hàm mã hóa/giải mã string. Mã độc phải gọi hàm giải mã string trước khi mỗi chuỗi string được sử dụng.

ảnh 19

Ví dụ về một hàm String_Decoder sau khi dữ liệu đã làm rối được push vào stack.

Dữ liệu rối thường được giải mã thành chuỗi string hữu ích ngay trên stack nên cách duy nhất để theo dõi chuỗi string đó là quan sát stack khi hàm giải mã hoàn thành thực thi. Vị trí tốt nhất để đặt breakpoint quan sát mọi string là vị trí cuối hàm giải mã. Với breakpoint này, mỗi lần click Play trong OllyDbg, chương trình sẽ tiếp tục thực thi và dừng khi một chuỗi string được giải mã.

Breakpoint có điều kiện

Breakpoint có điều kiện là các breakpoint mềm và chúng chỉ dừng thực thi khi một điều kiện cụ thể được thỏa mãn. OllyDbg cho phép đặt breakpoint có điều kiện bằng các biểu thức; mỗi lần chương trình thực thi đến breakpoint mềm, biểu thức sẽ được đánh giá và nếu kết quả biểu thức khác 0 (non-zero), chương trình sẽ dừng thực thi.

Cần cân nhắc kỹ khi đặt breakpoint có điều kiện vì chúng khiến chương trình chạy chậm đi rất nhiều và nếu ta không đặt đúng điều kiện, chương trình có thể sẽ không bao giờ dừng lại tại breakpoint đó.

Breakpoint có điều kiện đặc biệt hữu ích khi muốn chương trình dừng thực thi chỉ khi một tham số cụ thể được truyền vào một hàm API được gọi thường xuyên.

Ta có thể đặt một breakpoint có điều kiện để phát hiện các vùng nhớ được cấp phát với kích thước vượt quá một mức cụ thể. Poison Ivy là một backdoor trứ danh, nó nhận lệnh C&C từ Internet: lệnh C&C này được triển khai trong shellcode và  Poison Ivy cấp phát bộ nhớ để lưu trữ shellcode mà nó nhận được từ Internet. Hầu hết các vùng nhớ được yêu cầu cấp phát bởi Poison Ivy thường có kích thước nhỏ và không mấy đáng chú ý, trừ khi C&C server gửi đi một lượng lớn shellcode.

Cách tốt nhất để tìm ra vùng nhớ mà Poison Ivy yêu cầu cấp phát để lưu shellcode đó là đặt một breakpoint có điều kiện tại hàm VirtualAlloc của Kernel32.dll. Đây là hàm API mà Poison Ivy sử dụng để cấp phát động; nếu ta đặt một breakpoint với điều kiện là kích thước bộ nhớ cấp phát động lớn hơn hoặc bằng 100 bytes, mã độc sẽ không dừng thực thi đối với các trường hợp cấp phát động với kích thước nhỏ hơn 100 bytes.

Để đặt bẫy Poison Ivy, ta đặt một breakpoint chuẩn tại đầu hàm VirtualAlloc để thực thi tới khi gặp breakpoint này.

Hình bên trên minh họa đỉnh stack với 5 giá trị. Đầu tiên là địa chỉ trả về (00950079), sau đó là 4 tham số đầu vào cho hàm VirtualAlloc (Address, Size, AllocationType, Protect). Các tham số này được gán nhãn ngay cạnh địa chỉ và giá trị của chúng. Ví dụ, 0x29 bytes sẽ được cấp phát. Vì ESP trỏ tới đỉnh stack nên để truy cập giá trị của trường Size, ta cần tham chiếu nó trong bộ nhớ với giá trị [ESP+8] (0x00C3FDB8, ESP = 0x00C3FDB0).

Hình bên dưới là cửa sổ dịch ngược khi chương trình dừng thực thi tại breakpoint đầu hàm VirtualAlloc. Ta đặt điều kiện cho breakpoint là [ESP+8]>100 để phát hiện khi nào thì Poison Ivy nhận một shellcode kích thước lớn.

  1. Click phải tại lệnh đầu tiên của hàm VirtualAlloc, chọn Breakpoint > Conditional.
  2. Đặt biểu thức [ESP+8]>100 và click OK.
  3. Play và chờ tới khi mã độc dừng thực thi.

Breakpoint cứng

OllyDbg cũng cho phép đặt các breakpoint cứng thông qua một số thanh ghi cụ thể.

Ưu điểm của các breakpoint cứng là chúng không can thiệp vào code, stack hay bất kì tài nguyên nào của chương trình cần debug. Chúng cũng không làm chậm quá trình thực thi chương trình. Tuy nhiên, ta chỉ có thể đặt tối đa 4 breakpoint cứng tại cùng một thời điểm.

Để đặt breakpoint cứng tại một lệnh, click phải vào lệnh đó và chọn Breakpoint > Hardware, on Execution.

Mặc định, OllyDbg sẽ sử dụng các breakpoint mềm. Ta có thể thay đổi mặc định này trong tùy chọn Debugging Options. Dùng breakpoint cứng cũng giúp qua mặt một số kỹ thuật anti-debugging, khi các breakpoint mềm dễ dàng bị các kỹ thuật này phát hiện.

Breakpoint trên bộ nhớ

OllyDbg hỗ trợ đặt breakpoint cứng hoặc breakpoint mềm trên một vùng nhớ (chunk of memory) để dừng thực thi khi chương trình truy cập tới vùng nhớ đó. Các dạng truy cập phổ biến có thể là read, write, execute,…

Để đặt một breakpoint bộ nhớ, chọn một vùng nhớ trong cửa sổ memory dump hoặc một section trong memory map, click phải và chọn Breakpoint > Memory, on Access. Ta chỉ có thể đặt một breakpoint bộ nhớ tại một thời điểm và breakpoint nhớ trước đó sẽ bị xóa khi ta đặt một breakpoint nhớ mới.

OllyDbg triển khai các breakpoint nhớ mềm (sofware memory breakpoints) bằng cách thay đổi thuộc tính của khối ô nhớ (memory blocks) mà ta chọn. Tuy nhiên, kỹ thuật này không phải lúc nào cũng đáng tin cậy.

Ta nên hạn chế sử dụng các breakpoint nhớ, nhưng chúng sẽ rất hữu ích khi ta cần xác định khi nào một DLL nạp sẵn được sử dụng: ta có thể dùng breakpoint nhớ để dừng thực thi ngay khi code của DLL được gọi:

  1. Mở cửa sổ Memory Map và click phải lên .text section của DLL, chọn Set Memory Breakpoint on Access.
  2. F9 hoặc click nút Play để tiếp tục thực thi.

Chương trình sẽ dừng thực thi khi truy cập tới .text section của DLL ta vừa chọn.

Nạp các DLL

Vì các DLL không được thực thi trực tiếp, OllyDbg sử dụng một chương trình giả (dummy) gọi là loaddll.exe để nạp chúng. Kỹ thuật này rất hiệu quả vì mã độc thường được đóng gói như một DLL. Mặc định, OllyDbg ngắt thực thi tại DLL entry point (DllMain).

Để gọi các hàm export có tham số, đầu tiên ta cần nạp DLL đó vào OllyDbg, click nút Play để chạy hàm DllMain và dừng thực thi tại DLL entry point. Sau đó, ta có thể debug các export cùng tham số đầu vào của chúng bằng tùy chọn Debug > Call DLL Export.

Ví dụ, ta nạp ws2_32.dll vào OllyDbg và gọi hàm ntohl, hàm có chức năng chuyển đổi một số 32-bit từ network byte order sang host byte order. Ta có thể thêm bất kì tham số nào nếu cần thiết, chẳng hạn như 0x7F000001.

Ta có thể quan sát các lệnh hợp ngữ của hàm ntohl nếu click nút Follow in Disassembler. Nếu chọn Hide on call, cửa sổ Call export DLL này sẽ được ẩn đi sau khi ta thực hiện lời gọi hàm (Call). Tùy chọn Pause after call cho phép dừng thực thi ngay sau khi hàm ntohl được gọi, tương tự như một breakpoint.

Sau khi làm chủ các tùy chọn, click Call để thực thi hàm với tham số đã cung cấp và theo dõi thay đổi của các thanh ghi trước và sau khi thực hiện lời gọi hàm.  Để debug hàm export này, cần chắc chắn rằng đã đặt breakpoint hoặc chọn Pause after call trước khi Call. Trong hình dưới, ta quan sát được kết quả trả về của hàm ntohl được lưu trong EAX (0x0100007F, tương ứng với 127.0.0.1 dạng host byte order).

Tracing (Truy vết)

OllyDbg hỗ trợ nhiều tính năng truy vết đa dạng như standard back trace, call stack trace và run trace.

Standard Back Trace

Bất cứ khi nào ta nhảy trong cửa sổ dịch ngược với tùy chọn Step Into và Step Over, OllyDbg đều ghi lại sự dịch chuyển đó. Ta có thể nhấn phím – để lùi bước và quan sát lại các lệnh mà ta vừa thực hiện; hoặc nhấn phím + để tiếp tục thực thi. Nếu sử dụng Step Into, ta có thể truy vết lại mỗi bước nhảy (từng câu lệnh). Nếu sử dụng Step Over, ta chỉ có thể lùi lại vùng mà mình vừa nhảy qua; ta không thể lùi lại và quyết định Step Into tới các vùng khác.

Call Stack

Ta có thể sử dụng OllyDbg để quan sát luồng thực thi (execution path – phân biệt với thread) của một hàm cụ thể thông qua call stack trace. Để theo dõi một call stack, chọn View > Call Stack. Ta sẽ thấy một cửa sổ hiển thị thứ tự các lời gọi hàm để thực thi tới vị trí hiện tại.

Để dịch chuyển trên call stack, click đúp Address hoặc Called From trong cửa sổ Call Stack.

Run Trace

Một run trace cho phép ta thực thi code và OllyDbg sẽ lưu mọi lệnh thực thi và các thay đổi trên mọi thanh ghi cũng như các cờ. Các cách để thực hiện run trace:

  • Chọn đoạn code muốn run trace trên cửa sổ dịch ngược, click phải, chọn Run Trace > Add Selection. Sau khi đoạn code đó được thực thi, chọn View > Run Trace để quan sát các lệnh vừa được thực thi. Sử dụng phím hoặc + để di chuyển giữa các lệnh. Bằng cách này, ta có thể theo dõi các thay đổi trên mọi thanh ghi đối với từng lệnh.
  • Sử dụng tùy chọn Trace IntoTrace Over. Các tùy chọn này có thể dễ sử dụng hơn Add Selection vì ta không cần phải chọn đoạn code cần tracing.Trace Into sẽ thực hiện step into và ghi lại mọi lệnh được thực thi cho đến khi gặp breakpoint. Trace Over sẽ chỉ ghi lại các lệnh trong hàm mà ta đang thực thi.

Nếu sử dụng Trace Into và Trace Over mà không đặt breakpoint, OllyDbg sẽ cố thực hiện trace trên toàn bộ chương trình, điều này gây tốn thời gian và bộ nhớ một cách vô ích.

  • Chọn Debug > Set Condition. Ta có thể trace cho tới khi điều kiện được thỏa mãn và chương trình dừng thực thi. Cách này hữu ích khi ta muốn dừng tracing bằng một điều kiện cho trước và back trace lại từ vị trí thỏa mãn điều kiện để theo dõi cách mà điều kiện được thỏa mãn.

Xử lý ngoại lệ

Mặc định, nếu một ngoại lệ xảy ra trong lúc được debug, chương trình sẽ dừng thực thi và ưu tiên trao quyền điều khiển cho OllyDbg. OllyDbg có thể xử lý ngoại lệ hoặc chuyển nó cho chương trình. OllyDbg sẽ dừng thực thi khi gặp một ngoại lệ và ta có thể quyết định chuyển ngoại lệ cho chương trình xử lý bằng một trong các cách sau:

  • Shift+F7: thực hiện step into đối với ngoại lệ
  • Shift+F8: step over đối với ngoại lệ
  • Shift+F9: chạy exception handler

OllyDbg cung cấp một số tùy chọn xử lý ngoại lệ để bỏ qua một số ngoại lệ cụ thể và chuyển chúng cho chương trình. Trong hầu hết trường hợp phân tích mã độc, ta nên bỏ qua mọi ngoại lệ vì ta không debug chương trình với mục đích sửa các lỗi của nó.

Patching

OllyDbg cho phép ghép code vào một chương trình (assemble/patch). Ta có thể sửa đổi các lệnh hoặc giá trị ô nhớ bằng cách bôi đậm một vùng (code hoặc vùng nhớ), click phải và chọn Binary > Edit. Cửa sổ Edit code hoặc Edit data cho phép sửa đổi opcode hoặc dữ liệu tương ứng (OllyDbg có chức năng tự động điền các entry 00 hoặc lệnh NOP).

Ví dụ dưới minh họa một đoạn code thực hiện kiểm tra key để cho phép cấu hình mã độc. Một lệnh kiểm tra và lệnh nhảy có điều kiện (JNZ) tại (1) kiểm tra key có hợp lệ hay không. Nếu lệnh nhảy được thực thi, mã độc sẽ in chuỗi “Bad key”; nếu không, nó in chuỗi “Key Accepted!”. Cách đơn giản để ép mã độc thực thi theo nhánh key hợp lệ là sử dụng kỹ thuật patching. Ta chọn lệnh nhảy có điều kiện, click phải, Binary > Fill with NOPs. Thao tác này sẽ sửa lệnh JNZ thành các lệnh NOPs và mã độc sẽ cho rằng nó đã nhận được key hợp lệ.

Thao tác patching này chỉ gây ra thay đổi trên bộ nhớ và chỉ có hiệu lực đối với tiến trình đang chạy. Ta có thể lưu patching này bằng cách copy các sửa đổi vào một file thực thi mới. Click phải vào cửa sổ dịch ngược tại vị trí ta vừa patch, chọn Copy to Executable > All Modifications. Mọi sửa đổi vừa thực hiện sẽ được hiển thị trong một cửa sổ pop-up, ta chọn Save File.

Như vậy, file thực thi ta vừa lưu sẽ chấp nhận mọi trường hợp key. Kỹ thuật này hữu ích khi ta cần sửa đổi vĩnh viễn một mã độc để dễ phân tích hơn.

Phân tích Shellcode

Ta có thể sử dụng OllyDbg để phân tích shellcode:

  1. Copy shellcode từ một hex editor vào clipboard.
  2. Trong Memory map (khi đang debug một chương trình “vật chủ” nào đó), chọn vùng nhớ có dạng Priv (private memory – vùng nhớ riêng được chỉ định cho tiến trình, khác với các image read-only của file thực thi được chia sẻ đa tiến trình).

3. Double-click lên các hàng trong Memory map để quan sát nội dung hex dump. Vùng nhớ này thường chứa vài trăm byte 0 liền nhau.

4. Click phải lên vùng nhớ vừa chọn trong cửa sổ Memory map, Set Access > Full Access để đặt quyền read, write và execute.

5. Trong cửa sổ Dump, bôi đậm vùng các byte 0 liền nhau với kích thước đủ lớn để chứa shellcode, click phải, Binary > Binary Paste. Thao tác này thực hiện paste shellcode từ clipboard vào vùng nhớ vừa chọn.

6. Đặt thanh ghi EIP trỏ tới vùng nhớ vừa sửa đổi. Thao tác này có thể thực hiện đơn giản bằng cách click phải vào một lệnh trong cửa sổ dịch ngược và chọn New Origin Here.

Từ đây, ta có thể thực thi và debug shellcode như một chương trình bình thường.

Các plug-in

http://www.openrce.org/downloads/browse/OllyDbg_Plugins

Các plugin cho OllyDbg được triển khai dưới dạng DLL đặt trong thư mục gốc của OllyDbg.

OllyDump

OllyDump là plugin phổ biến nhất, cho phép dump một tiến trình thành PE file. OllyDump cũng thường được dùng trong quá trình unpacking. Khi sử dụng OllyDump để dump một tiến trình, ta có thể đặt entry point và offset cho các section một cách thủ công; nhưng thường thì ta nên để OllyDump tự động thực hiện thao tác trên.

Hide Debugger

Plugin này thực hiện một số phương pháp giúp OllyDbg qua mặt các cơ chế phát hiện debugger. Hide Debugger đặc biệt hiệu quả đối với các hàm kiểm tra IsDebuggerPresent, FindWindow, các bẫy sử dụng ngoại lệ không được xử lý, và khai thác lỗ hổng OutputDebugString của OllyDbg.

Command Line

Command Line cho phép ta truy cập OllyDbg qua giao diện dòng lệnh, tương tự như WinDbg. Các lệnh phổ biến có thể dùng qua Command Line:

Bookmarks

Plugin này cho phép đánh dấu các vùng nhớ quan trọng giúp truy cập chúng dễ dàng mà không cần phải nhớ địa chỉ cụ thể. Để thêm một bookmark, click phải trong cửa sổ dịch ngược, Bookmark > Insert Bookmark. Để hiển thị tất cả các bookmark, Plugins > Bookmarks > Bookmarks.

Phân Tích Mã Độc Sử Dụng IDA PRO (Phần 1)

Đón đọc thêm nhiều tin tức về công nghệ TẠI ĐÂY

0