Intel Pin
Instrumentation system được hiểu là hệ thống đo đạc, theo dõi và điều khiển một tiến trình. Pin là một binary instrumentation system (do Intel xây dựng ) cho phép người sử dụng có thể inject các đoạn mã kiểm tra (tạm gọi là introspection code) vào một vị trí bất kỳ trong một tiến trình đang chạy. Các đoạn mã kiểm tra này có tác dụng quan sát hành vi của một tiến trình. Các tham số có thể quan sát bao gồm nội dung của tất cả các thanh ghi và giá trị của một ô nhớ bất kì tại một thời điểm. Introspection code được xây dựng dựa trên Intel Pin framework, được gọi là Pin tool. Pin tool được xây dựng để phục vụ nhiều mục đích khác nhau như: application profiling, phát hiện memory leak, ... Intel pin hỗ trợ cả 2 nền tảng 32bit và 64bit, có thể chạy trên Microsoft Windows, Linux, OSX và Android.
Tổng quan
Instrumentation là gì?
Instrumentation là phương pháp thêm một số đoạn mã trung gian ( tạm gọi là Instrumentation Code - IC) vào chương trình để lấy các thông tin của chương trình trong quá trình thực thi. Để thực hiện Instrumentation, có 2 phương pháp chính đang được sử dụng hiện nay:
- Source Code Instrumentation (SCI): Tức là thêm IC vào chương trình trước khi biên dịch. Quá trình này chỉ thực hiện được nếu có source code của chương trình. Đây là phương pháp instrumentation mà American Fuzzy Lop (AFL Fuzzer) của lcamtuf đang sử dụng.
- Binary Instrumentation (BI): thêm IC vào chính file thực thi của chương trình. BI cũng có 2 phương pháp có thể thực hiện:
- Static Instrumentation: Sử dụng một chương trình disassembler hay emulator để ghi các đoạn mã IC vào trong binary nhưng không làm thay đổi hoạt động của chương trình ban đầu.
- Dynamic Instrumentation: Chèn IC vào chương trình trong quá trình thực thi, không làm ảnh hưởng tới file binary ban đầu. Đây là cách tiếp cận của Pin framework.
Ví dụ về instrumentation
Một ví dụ về SCI
Trước khi thêm IC
void foo(int x){ switch (x){ case 0: printf("devel\n"); break; case 1: printf("branch\n"); break; default: printf("Invalid\n"); break; } }
Sau khi thêm IC
bool sci[5]; void foo(int x){ sci[0] = true; switch (x){ case 0: sci[1] = true; printf("devel\n"); break; case 1: sci[2] = true; printf("branch\n"); break; default: sci[3] = true; printf("Invalid\n"); break; } sci[4] = true; }
Ví dụ về BI
count++ xor ebx, ebx count++ mov eax, 1337h count++ xor eax, ebx
Ưu điểm và nhược điểm của sử dụng Pin tool ( hoặc các Dynamic Instrumentation Tool khác):
Ưu điểm:
- Có thể thực hiện instrumentation với bất cứ một chương trình nào.
- Không cần biết mã nguồn của chương trình.
- Rất linh động, do có thể tùy trình được ( bằng code) trong quá trình instrumentation
Nhược điểm:
- Hiệu năng thấp.
Xây dựng Pin tool
Tải Intel Pin framework
Để thực hiện xây dựng một pintool cho riêng mình, cần tải công cụ Intel Pin Framework từ trang chủ của Intel:
https://software.intel.com/en-us/articles/pin-a-dynamic-binary-instrumentation-tool
Tại thời điểm viết bài này, Intel đang duy trì 2 phiên bản: Bản 3.0 và bản 2.14.
Xây dựng Tool
Chúng ta nên bắt đầu bằng một ví dụ kinh điển nhất của Pin framework: Simple Instruction Count ( tham khảo source/tools/SimpleExample/icount.cpp và source/tools/ManualExample/inscount0.cpp )
#include "pin.H" #include <iostream> UINT64 ins_count = 0; // biến lưu lại số lượng instruction đã đi qua INT32 Usage() { cerr << "This tool prints out the number of dynamic instructions executed to stderr.\n \n"; cerr << KNOB_BASE::StringKnobSummary(); cerr << endl; return -1; } VOID docount() { // analysis routine ins_count++; // biến được tăng lên trước khi 1 instruction được thực thi } VOID Instruction(INS ins, VOID *v) { // instrumentation routine /* hàm được gọi bởi Pin framework pin framework sẽ thực hiện các bước như sau: - chuyển stack của chương trình về pin stack - lưu lại toàn bộ các thanh ghi - gọi hàm docount - khôi phục toàn bộ các giá trị thanh ghi - chuyển stack về stack của chương trình */ /* INS_InsertCall nhận các tham số sau: tham số 1 : Instruction được instrument tham số 2 : Vị trí của hàm docount được thực thi : IPOINT_BEFORE trước khi thực hiện lệnh IPOINT_AFTER sau khi thực hiện lệnh, thường không valid với các lệnh rẽ nhánh IPOINT_TAKEN_BRANCH sau khi thực hiện lệnh rẽ nhánh tham số 3 : hàm phân tích, trong ví dụ này là docount từ tham số thứ 4 trở đi là các tham số của hàm docount. Kết thúc của chuỗi này là giá trị IARG_END. Nên tham khảo IARG_TYPE */ INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END); } VOID Fini(INT32 code, VOID *v) { // hàm này được gọi khi đã kết thúc chương trình cout << "Count " << ins_count << endl; } /*entry point của pin tool */ int main(int argc, char *argv[]) { // phân tích các tham số được truyền vào cho pin tool ( không phải cho chương trình) if( PIN_Init(argc,argv) ) { return Usage(); } // đăng kí hàm callback để instrument INS_AddInstrumentFunction(Instruction, 0); // đăng kí hàm callback khi chương trình chính kết thúc PIN_AddFiniFunction(Fini, 0); // Never returns PIN_StartProgram(); // bắt đầu thực thi chương trình và instrument nó return 0; }
Trong trường hợp hàm docount muốn có nhiều tham số đầu vào hơn, thì viết như sau:
/*đọc thêm giá trị của thanh ghi PC mỗi khi thực thi instruction */ VOID docount(VOID * ip ) { // analysis routine cout << "pc:"<<ip<<endl; ins_count++; // biến được tăng lên trước khi 1 instruction được thực thi } VOID Instruction(INS ins, VOID *v) { // instrumentation routine INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_INST_PTR, IARG_END); }
Trong ví dụ trên IARG_INST_PTR sẽ được pin tool hiểu là truyền tham số Instruction Pointer cho hàm docount. Hàm này cũng phải khai báo sẽ nhận thêm tham số VOID *ip để xử lý.
Một ví dụ khác:
/* Đọc thêm giá trị của thanh ghi PC mỗi khi thực thi instruction Đọc thêm giá trị của thanh ghi EAX (hoặc RAX) khi thực thi */ VOID docount(VOID * ip, VOID * gax ) { // analysis routine cout << "pc:"<<ip<<endl; cout << "GAX:"<<gax<<endl; ins_count++; // biến được tăng lên trước khi 1 instruction được thực thi } VOID Instruction(INS ins, VOID *v) { // instrumentation routine INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_INST_PTR, // tương ứng với tham số VOID * ip IARG_REG_VALUE, REG_GAX, // tương ứng với tham số VOID * gax IARG_END); }
Trong ví dụ trên, cặp giá trị IARG_REG_VALUE và REG_GAX sẽ được pin hiểu là truyền tham số có nội dung của thanh ghi EAX hoặc RAX cho hàm docount. Hàm này cũng phải khai báo sẽ nhận thêm tham số này.
Có thể sử dụng tường minh là REG_EAX hoặc REG_RAX. Môi trường 32bit, chỉ tồn tại EAX, trên môi trường 64bit mới có cả 2 thanh ghi này ( EAX là 1 phần của RAX). Do đó, để code build không phụ thuộc vào môi trường, nên dùng giá trị REG_GAX.
Build tool
Intel có đưa kèm rất nhiều ví dụ để viết một tool dựa vào Pin.Các tool này nằm trong thư mục source/tools/ManualExample, source/tools/SimpleExample
- Build với gcc
- Build với windows: Trang chủ của Intel yêu cầu cài cygwin để build. Tuy nhiên, mình vốn không thích cài nhiều nên mình sẽ hướng dẫn cách build mà không cần cygwin.
Build pin tool với Microsoft Visual Studio
Intel build sẵn pin thành các file lib, ở chế độ Release. Các cấu hình dưới đây sẽ dành cho profile Release trong project của Visual Studio.
Tạo một project có dạng Win32 DLL
Giải nén pin tải được vào thư mục pin-windows, cùng cấp với solution của project trên
Thiết lập các Preprocessor tại nhánh C/C++ > Preprocessor như sau:
TARGET_WINDOWS;TARGET_IA32;HOST_IA32;_SECURE_SCL=0;USING_XED;NDEBUG
Cấu hình Runtime Library là MT: C/C++ > Code Generation:
Multi-threaded (/MT)
Cấu hình các thư mục chứa file header của pin : C/C++ > General > Additional Include Directories
"$(SolutionDir)..\pin-windows\source\include\pin";"$(SolutionDir)..\pin-windows\source\include\pin\gen";"$(SolutionDir)..\pin-windows\extras\xed-ia32\include";"$(SolutionDir)..\pin-windows\extras\components\include"
Cấu hình thư mục chứa lib để link: Linker > General > Additional Library Directories
"$(SolutionDir)..\pin-windows\ia32\lib";"$(SolutionDir)..\pin-windows\ia32\lib-ext";"$(SolutionDir)..\pin-windows\extras\xed-ia32\lib"
Cấu hình thư viện dependency:
Linker > Input > Additional Dependencies
ntdll-32.lib libxed.lib pin.lib pinvm.lib libcmt.lib libcpmt.lib
Linker > Input > Ignore All Default Libraries
Yes (/NODEFAULTLIB)
Cấu hình Entry point: Linker > Advanced > Entry Point
Ptrace_DllMainCRTStartup@12
Sử dụng tool
Để sử dụng pin chạy lệnh sau:
pin -t path/to/pintool.so -- path/to/application [application args]
Nếu chạy ở chế độ Inject code, thì sử dụng lệnh sau:
pin -t path/to/pintool.so --pid 1234
Khi chơi CTF, hãy nghĩ đến pin tool
Một ví dụ khiến người ta nghĩ đến instruction counter và pin tool là khi chơi CTF. Một kĩ thuật khá hay trong khi giải các bài CTF dạng reverse engineering là Side channel attack. Thông thường, các bài reverse sẽ có những trick rất khó để vượt qua trong thời gian ngắn. Dạng phổ biến nhất, thường là cho người chơi input chuỗi kí tự, qua nhiều phép biến đổi lằng nhằng sẽ kiểm tra xem có đúng là input hợp lệ hay không. Nếu hợp lệ thì in ra cờ. Phép kiểm tra này thường duyệt qua từng kí tự. Nếu kí tự đó khớp thì mới kiểm tra kí tự kế tiếp. Chính vì vậy, người ta sẽ input từng kí tự (printable characters chẳng hạn) và đếm số lượng instruction đã thực thi đối với kí tự đó. Nếu gặp 1 kí tự có số instruction đột biến so với các kí tự còn lại, đó là kí tự của flag. Writeup của Vulnhub Team đã thể hiện rất rõ điều này (rất may là mình ko phải viết lại vào đây, vì mình lười). Cám ơn Vulnhub Team. : https://ctf-team.vulnhub.com/picoctf-2014-baleful/
Vấn đề hiệu năng
Có một paper rất hay nói về các vấn đề khi build một công cụ phân tích chương trình bằng phương pháp BI, đồng thời, paper có so sánh các framework với nhau: Building Customized Program Analysis Tools with Dynamic Instrumentation
Trong bài kế tiếp mình sẽ làm rõ hơn về kĩ thuật xây dựng pin tool.