อ่าน 11 นาที
การเกี่ยว
ใน การเขียนโปรแกรมคอมพิวเตอร์ การดักจับ (hooking) คือเทคนิคต่างๆ ที่ใช้ในการเปลี่ยนแปลงหรือเสริมพฤติกรรมของ ระบบปฏิบัติการ แอ ปพลิเคชัน หรือส่วนประกอบซอฟต์แวร์อื่นๆ โดยการดักจับ...
การเกี่ยว
ในการเขียนโปรแกรมคอมพิวเตอร์การดักจับ (hooking)คือเทคนิคต่างๆ ที่ใช้ในการเปลี่ยนแปลงหรือเสริมพฤติกรรมของระบบปฏิบัติการแอปพลิเคชันหรือส่วนประกอบซอฟต์แวร์อื่นๆ โดยการดักจับการเรียกใช้ฟังก์ชันข้อความหรือเหตุการณ์ที่ส่งผ่านระหว่างส่วนประกอบซอฟต์แวร์โค้ดที่จัดการกับการเรียกใช้ฟังก์ชัน เหตุการณ์ หรือข้อความที่ถูกดักจับเหล่านี้เรียกว่าฮุก (hook )
เมธอดฮุกมีความสำคัญอย่างยิ่งในรูปแบบเมธอดเทมเพลตซึ่งโค้ดทั่วไปในคลาสแบบนามธรรมสามารถเสริมด้วยโค้ดที่กำหนดเองในคลาสย่อยได้ ในกรณีนี้ เมธอดฮุกแต่ละตัวจะถูกกำหนดไว้ในคลาสแบบนามธรรมโดยมีการใช้งานที่ว่างเปล่า ซึ่งจะช่วยให้สามารถใส่การใช้งานที่แตกต่างกันในแต่ละคลาสย่อยที่เป็นรูปธรรมได้
การใช้ Hooking มีจุดประสงค์หลายอย่าง รวมถึงการดีบักและการขยายฟังก์ชันการทำงาน ตัวอย่างเช่น การดักจับข้อความเหตุการณ์จากแป้นพิมพ์หรือเมาส์ก่อนที่จะถึงแอปพลิเคชัน หรือการดักจับการเรียกใช้ระบบปฏิบัติการเพื่อตรวจสอบพฤติกรรมหรือแก้ไขฟังก์ชันของแอปพลิเคชันหรือส่วนประกอบอื่นๆ นอกจากนี้ยังใช้กันอย่างแพร่หลายในโปรแกรมวัดประสิทธิภาพ เช่น การวัด อัตราเฟรมในเกม 3 มิติ ซึ่งการรับส่งข้อมูลจะทำผ่าน Hooking
การดักจับข้อมูลยังสามารถถูกใช้โดยโค้ดที่เป็นอันตรายได้อีกด้วย ตัวอย่างเช่นรูทคิตซึ่งเป็นซอฟต์แวร์ที่พยายามทำให้ตัวเองมองไม่เห็นโดยการปลอมแปลงผลลัพธ์ของ การเรียกใช้ APIที่จะเปิดเผยตัวตนของมัน มักใช้เทคนิคการดักจับข้อมูลนี้
วิธีการ
โดยทั่วไปแล้ว การแทรก hook จะทำในขณะที่ซอฟต์แวร์กำลังทำงานอยู่ แต่การแทรก hook ก็เป็นกลยุทธ์ที่สามารถใช้ได้ก่อนที่แอปพลิเคชันจะเริ่มต้นทำงานด้วยเช่นกัน เทคนิคทั้งสองนี้จะอธิบายรายละเอียดเพิ่มเติมด้านล่าง
การแก้ไขแหล่งที่มา
การดักจับการทำงาน (Hooking) สามารถทำได้โดยการแก้ไขซอร์สโค้ดของไฟล์ปฏิบัติการหรือไลบรารีก่อนที่แอปพลิเคชันจะทำงาน โดยใช้วิธีการวิศวกรรมย้อนกลับ (Reverse Engineering ) โดยทั่วไปแล้วจะใช้เพื่อดักจับการเรียกใช้ฟังก์ชัน เพื่อตรวจสอบหรือแทนที่ฟังก์ชันเหล่านั้นทั้งหมด
ตัวอย่างเช่น การใช้โปรแกรมถอดรหัส (disassembler)จะช่วยให้สามารถค้นหาจุดเริ่มต้นของฟังก์ชันภายในโมดูลได้ จากนั้นจึงทำการแก้ไขเพื่อให้โหลดโมดูลไลบรารีอื่นแบบไดนามิก และเรียกใช้เมธอดที่ต้องการภายในไลบรารีที่โหลดมานั้น อีกวิธีหนึ่งที่เกี่ยวข้องกับการดักจับ (hooking) คือการแก้ไขตารางการนำเข้า( import table )ของไฟล์ปฏิบัติการ ตารางนี้สามารถแก้ไขได้เพื่อโหลดโมดูลไลบรารีเพิ่มเติม รวมถึงเปลี่ยนโค้ดภายนอกที่จะถูกเรียกใช้เมื่อแอปพลิเคชันเรียกใช้ฟังก์ชัน
อีกวิธีหนึ่งในการดักจับการเรียกใช้ฟังก์ชันคือการดักจับการเรียกใช้ฟังก์ชันผ่านไลบรารีตัวห่อ (wrapper library ) ไลบรารีตัวห่อคือเวอร์ชันของไลบรารีที่แอปพลิเคชันโหลด โดยมีฟังก์ชันการทำงานเหมือนกับไลบรารีต้นฉบับที่จะถูกแทนที่ทุกประการ กล่าวคือ ฟังก์ชันทั้งหมดที่สามารถเข้าถึงได้นั้นเหมือนกันระหว่างไลบรารีต้นฉบับและไลบรารีที่ถูกแทนที่ ไลบรารีตัวห่อนี้สามารถออกแบบให้เรียกใช้ฟังก์ชันการทำงานใดๆ จากไลบรารีต้นฉบับ หรือแทนที่ด้วยชุดตรรกะใหม่ทั้งหมดก็ได้
การห่อสัญลักษณ์
GNU Linkerรองรับ--wrap=fooตัวเลือก CLI เมื่อใช้งานแล้ว ตัวเชื่อมโยงจะแทนที่การเรียกใช้ทั้งหมดไปยังfooด้วยการเรียกใช้ไปยัง__wrap_fooคำจำกัดความดั้งเดิมของ สามารถอ้างอิงได้ ด้วยfooสัญลักษณ์[ 1 ]เนื่องจากวิธีนี้ไม่ได้ใช้ตัวเชื่อมโยงแบบไดนามิกวิธีนี้จึงสามารถใช้ได้เฉพาะเมื่อมีซอร์สโค้ดของโปรแกรมและขั้นตอนการเชื่อมโยงของการสร้างสามารถแก้ไขได้ __real_foo
การแก้ไขขณะรันไทม์
ระบบปฏิบัติการและซอฟต์แวร์อาจมีวิธีการแทรก event hook ได้ง่ายๆ ในระหว่างการทำงานโดยจะต้องได้รับอนุญาตอย่างเพียงพอจากกระบวนการ ที่แทรก hook นั้น ตัวอย่างเช่น Microsoft Windowsอนุญาตให้ผู้ใช้แทรก hook ที่สามารถใช้ประมวลผลหรือแก้ไขเหตุการณ์ ของระบบ และเหตุการณ์ของแอปพลิเคชันสำหรับกล่องโต้ตอบแถบเลื่อนและเมนูรวมถึงรายการอื่นๆ นอกจากนี้ยังอนุญาตให้ hook แทรก ลบ ประมวลผล หรือแก้ไข เหตุการณ์ ของแป้นพิมพ์และเมาส์ได้ Linux ก็เป็นอีกตัวอย่างหนึ่งที่สามารถใช้ hook ในลักษณะเดียวกันเพื่อประมวลผลเหตุการณ์เครือข่ายภายในเคอร์เนลผ่านNetFilterได้
เมื่อไม่มีฟังก์ชันการทำงานดังกล่าว การดักจับการเรียกใช้ฟังก์ชันในไลบรารีแบบพิเศษจะใช้วิธีนี้ การดักจับการเรียกใช้ฟังก์ชันจะทำได้โดยการเปลี่ยนคำสั่งโค้ดไม่กี่คำสั่งแรกของฟังก์ชันเป้าหมายให้กระโดดไปยังโค้ดที่ถูกแทรกเข้าไป หรืออีกทางเลือกหนึ่ง ในระบบที่ใช้แนวคิดไลบรารีที่ใช้ร่วมกัน สามารถแก้ไขตาราง เวกเตอร์การขัดจังหวะหรือตารางตัวอธิบายการนำเข้าในหน่วยความจำได้ โดยพื้นฐานแล้ว กลยุทธ์เหล่านี้ใช้แนวคิดเดียวกันกับการแก้ไขซอร์สโค้ด แต่เป็นการเปลี่ยนแปลงคำสั่งและโครงสร้างที่อยู่ในหน่วยความจำของกระบวนการเมื่อกระบวนการนั้นทำงานอยู่แล้ว
ตัวอย่างโค้ด
การเกี่ยวตารางวิธีเสมือน
เมื่อใดก็ตามที่คลาสกำหนด/สืบทอดฟังก์ชันเสมือน (หรือเมธอด) คอมไพเลอร์จะเพิ่มตัวแปรสมาชิกที่ซ่อนอยู่ลงในคลาสซึ่งชี้ไปยังตารางเมธอดเสมือน (VMT หรือ Vtable) คอมไพเลอร์ส่วนใหญ่จะวางตัวชี้ VMT ที่ซ่อนอยู่ไว้ที่ 4 ไบต์แรกของทุกอินสแตนซ์ของคลาส VMT โดยพื้นฐานแล้วคืออาร์เรย์ของตัวชี้ไปยังฟังก์ชันเสมือนทั้งหมดที่อินสแตนซ์ของคลาสอาจเรียกใช้ ในระหว่างรันไทม์ ตัวชี้เหล่านี้จะถูกตั้งค่าให้ชี้ไปยังฟังก์ชันที่ถูกต้อง เนื่องจากในระหว่างการคอมไพล์ยังไม่ทราบว่าจะเรียกใช้ฟังก์ชันพื้นฐานหรือจะเรียกใช้เวอร์ชันที่ถูกเขียนทับของฟังก์ชันจากคลาสที่สืบทอดมา (ซึ่งทำให้สามารถใช้โพลีมอร์ฟิซึมได้ ) ดังนั้น ฟังก์ชันเสมือนจึงสามารถถูกดักจับได้โดยการแทนที่ตัวชี้ไปยังฟังก์ชันเหล่านั้นภายใน VMT ใดๆ ที่ปรากฏ โค้ดด้านล่างแสดงตัวอย่างของการดักจับ VMT ทั่วไปใน Microsoft Windows ซึ่งเขียนด้วยภาษา C++ [ 2 ]
#include <iostream> #include "windows.h" using namespace std ; class VirtualClass { public : int number ; virtual void VirtualFn1 () // นี่คือฟังก์ชันเสมือนที่จะถูกดักจับ{ cout << "VirtualFn1 called " << number ++ << " \n\n " ; } }; using VirtualFn1_t = void ( __thiscall * )( void * thisptr ); VirtualFn1_t orig_VirtualFn1 ;void __fastcall hkVirtualFn1 ( void * thisptr , int edx ) // นี่คือฟังก์ชัน hook ของเรา ซึ่งเราจะทำให้โปรแกรมเรียกแทนฟังก์ชัน VirtualFn1 ดั้งเดิมหลังจากทำการ hook แล้ว{ cout << "Hook function called" << " \n " ; orig_VirtualFn1 ( thisptr ); // เรียกฟังก์ชันดั้งเดิม} int main () { VirtualClass * myClass = new VirtualClass (); // สร้างพอยเตอร์ไปยังอินสแตนซ์ของ VirtualClass ที่จัดสรรแบบไดนามิกvoid ** vTablePtr = * reinterpret_cast < void ***> ( myClass ); // ค้นหาที่อยู่ซึ่งชี้ไปยังฐานของ VMT ของ VirtualClass (ซึ่งชี้ไปยัง VirtualFn1) และเก็บไว้ใน vTablePtr DWORD oldProtection ; VirtualProtect ( vTablePtr , 4 , PAGE_EXECUTE_READWRITE , & oldProtection ); //ลบการป้องกันหน้าเพจที่จุดเริ่มต้นของ VMT เพื่อให้เราสามารถเขียนทับตัวชี้ตัวแรกได้orig_VirtualFn1 = reinterpret_cast < VirtualFn1_t > ( * vTablePtr ); //เก็บตัวชี้ไปยัง VirtualFn1 จาก VMT ไว้ในตัวแปรส่วนกลาง เพื่อให้สามารถเข้าถึงได้อีกครั้งในภายหลังหลังจากที่รายการใน VMT ถูก//เขียนทับด้วยฟังก์ชัน hook ของเรา* vTablePtr = & hkVirtualFn1 ; //เขียนทับตัวชี้ไปยัง VirtualFn1 ภายในตารางเสมือนด้วยตัวชี้ไปยังฟังก์ชัน hook ของเรา (hkVirtualFn1) VirtualProtect ( vTablePtr , 4 , oldProtection , 0 ); //กู้คืนการป้องกันหน้าเพจแบบเก่าmyClass -> VirtualFn1 (); //เรียกฟังก์ชันเสมือนจากอินสแตนซ์คลาสของเรา เนื่องจากตอนนี้มันถูก hook แล้ว การเรียกนี้จะเรียกฟังก์ชัน hook ของเรา (hkVirtualFn1) myClass -> VirtualFn1 (); myClass -> VirtualFn1 (); delete myClass ; ส่งคืนค่า0 ; }ฟังก์ชันเสมือนทั้งหมดต้องเป็นฟังก์ชันสมาชิกของคลาส และฟังก์ชันสมาชิกของคลาสทั้งหมด (ที่ไม่ใช่แบบสแตติก) จะถูกเรียกด้วยข้อกำหนดการเรียก __thiscall (เว้นแต่ฟังก์ชันสมาชิกนั้นรับอาร์กิวเมนต์จำนวนแปรผัน ซึ่งในกรณีนี้จะถูกเรียกด้วย __cdecl) ข้อกำหนดการเรียก __thiscall จะส่งพอยเตอร์ไปยังอินสแตนซ์ของคลาสที่เรียก (โดยทั่วไปเรียกว่าพอยเตอร์ "this") ผ่านรีจิสเตอร์ ECX (บนสถาปัตยกรรม x86) ดังนั้น เพื่อให้ฟังก์ชันฮุกสามารถดักจับพอยเตอร์ "this" ที่ส่งผ่านและรับเป็นอาร์กิวเมนต์ได้อย่างถูกต้อง ฟังก์ชันฮุกจะต้องตรวจสอบในรีจิสเตอร์ ECX ในตัวอย่างข้างต้นนี้ ทำได้โดยการตั้งค่าฟังก์ชันฮุก (hkVirtualFn1) ให้ใช้ข้อกำหนดการเรียก __fastcall ซึ่งจะทำให้ฟังก์ชันฮุกตรวจสอบในรีจิสเตอร์ ECX สำหรับอาร์กิวเมนต์ตัวใดตัวหนึ่ง
โปรดทราบด้วยว่า ในตัวอย่างข้างต้น ฟังก์ชัน hook (hkVirtualFn1) ไม่ใช่ฟังก์ชันสมาชิก ดังนั้นจึงไม่สามารถใช้รูปแบบการเรียก __thiscall ได้ ต้องใช้ __fastcall แทน เนื่องจากเป็นรูปแบบการเรียกเพียงรูปแบบเดียวที่ตรวจสอบรีจิสเตอร์ ECX เพื่อหาอาร์กิวเมนต์
ฮุคเหตุการณ์แป้นพิมพ์ C#
ตัวอย่างต่อไปนี้จะเชื่อมต่อกับเหตุการณ์แป้นพิมพ์ใน Microsoft Windows โดยใช้Microsoft .NET Framework
การใช้งานSystem.Runtime.InteropServices ;เนมสเปซHooks ;public class KeyHook { /* ตัวแปรสมาชิก */ protected static int Hook ; protected static LowLevelKeyboardDelegate Delegate ; protected static readonly object Lock = new object (); protected static bool IsRegistered = false ;/* การนำเข้า DLL */ [DllImport("user32")] private static extern int SetWindowsHookEx ( int idHook , LowLevelKeyboardDelegate lpfn , int hmod , int dwThreadId );[DllImport("user32")] private static extern int CallNextHookEx ( int hHook , int nCode , int wParam , KBDLLHOOKSTRUCT lParam );[DllImport("user32")] private static extern int UnhookWindowsHookEx ( int hHook );/* ประเภทและค่าคงที่ */ protected delegate int LowLevelKeyboardDelegate ( int nCode , int wParam , ref KBDLLHOOKSTRUCT lParam ); private const int HC_ACTION = 0 ; private const int WM_KEYDOWN = 0x0100 ; private const int WM_KEYUP = 0x0101 ; private const int WH_KEYBOARD_LL = 13 ;[StructLayout(LayoutKind.Sequential)] public struct KBDLLHOOKSTRUCT { public int vkCode ; public int scanCode ; public int flags ; public int time ; public int dwExtraInfo ; }/* เมธอด */ static private int LowLevelKeyboardHandler ( int nCode , int wParam , ref KBDLLHOOKSTRUCT lParam ) { if ( nCode == HC_ACTION ) { if ( wParam == WM_KEYDOWN ) System . Console . Out . WriteLine ( "Key Down: " + lParam . vkCode ); else if ( wParam == WM_KEYUP ) System . Console . Out . WriteLine ( "Key Up: " + lParam . vkCode ); } return CallNextHookEx ( Hook , nCode , wParam , lParam ); }public static bool RegisterHook () { lock ( Lock ) { if ( IsRegistered ) return true ; Delegate = LowLevelKeyboardHandler ; Hook = SetWindowsHookEx ( WH_KEYBOARD_LL , Delegate , Marshal . GetHINSTANCE ( System . Reflection . Assembly . GetExecutingAssembly (). GetModules ()[ 0 ] ). ToInt32 (), 0 );ถ้า( Hook != 0 ) ให้คืนค่าIsRegistered เป็นtrue ; Delegate เป็นnull ; คืนค่าfalse ; } }public static bool UnregisterHook () { lock ( Lock ) { return IsRegistered = ( UnhookWindowsHookEx ( Hook ) != 0 ); } } }การดักจับ/แทรกแซง API/ฟังก์ชันโดยใช้คำสั่ง JMP หรือที่เรียกว่าการต่อเชื่อม (splicing)
โค้ดต้นฉบับต่อไปนี้เป็นตัวอย่างของวิธีการดักจับ API/ฟังก์ชัน ซึ่งดักจับโดยการเขียนทับไบต์หกไบต์แรกของฟังก์ชัน ปลายทาง ด้วย คำสั่ง JMPไปยังฟังก์ชันใหม่ โค้ดจะถูกคอมไพล์เป็น ไฟล์ DLLจากนั้นโหลดเข้าสู่กระบวนการเป้าหมายโดยใช้วิธีการฉีด DLL ใดๆ ก็ได้ การใช้การสำรองข้อมูลของฟังก์ชันดั้งเดิมอาจทำให้สามารถกู้คืนไบต์หกไบต์แรกได้อีกครั้งเพื่อไม่ให้การเรียกถูกขัดจังหวะ ในตัวอย่างนี้ ฟังก์ชัน API ของ win32 MessageBoxW ถูกดักจับ[ 3 ]
/* แนวคิดนี้อิงตามแนวทางของ chrom-lib ซึ่งเผยแพร่ภายใต้ใบอนุญาต GNU LGPL แหล่งที่มาของ chrom-lib: https://github.com/linuxexp/chrom-lib ลิขสิทธิ์ (C) 2011 Raja Jamwal */ #include <windows.h> #define SIZE 6typedef int ( WINAPI * pMessageBoxW )( HWND , LPCWSTR , LPCWSTR , UINT ); // ต้นแบบกล่องข้อความint WINAPI MyMessageBoxW ( HWND , LPCWSTR , LPCWSTR , UINT ); // เส้นทางอ้อมของเราvoid BeginRedirect ( LPVOID ); pMessageBoxW pOrigMBAddress = NULL ; // ที่อยู่ของไบต์ ดั้งเดิม oldBytes [ SIZE ] = { 0 }; // ไบต์สำรองJMP [ SIZE ] = { 0 }; // คำสั่ง JMP ขนาด 6 ไบต์DWORD oldProtect , myProtect = PAGE_EXECUTE_READWRITE ;INT APIENTRY DllMain ( HMODULE hDLL , DWORD Reason , LPVOID Reserved ) { switch ( Reason ) { case DLL_PROCESS_ATTACH : // ถ้าแนบแล้วpOrigMBAddress = ( pMessageBoxW ) GetProcAddress ( GetModuleHandleA ( "user32.dll" ), // รับที่อยู่ของ "MessageBoxW" เดิม); if ( pOrigMBAddress != NULL ) BeginRedirect ( MyMessageBoxW ); // เริ่มการเปลี่ยนเส้นทางbreak ;กรณีDLL_PROCESS_DETACH : VirtualProtect (( LPVOID ) pOrigMBAddress , SIZE , myProtect , & oldProtect ); // กำหนดการป้องกันการอ่านและการเขียนmemcpy ( pOrigMBAddress , oldBytes , SIZE ); // กู้คืนข้อมูลสำรองVirtualProtect (( LPVOID ) pOrigMBAddress , SIZE , oldProtect , & myProtect ); // รีเซ็ตการป้องกันกรณีDLL_THREAD_ATTACH : กรณีDLL_THREAD_DETACH : break ; } return TRUE ; }void BeginRedirect ( LPVOID newFunction ) { BYTE tempJMP [ SIZE ] = { 0xE9 , 0x90 , 0x90 , 0x90 , 0x90 , 0xC3 }; // 0xE9 = JMP 0x90 = NOP 0xC3 = RET memcpy ( JMP , tempJMP , SIZE ); // เก็บคำสั่ง JMP ลงใน JMP DWORD JMPSize = (( DWORD ) newFunction - ( DWORD ) pOrigMBAddress - 5 ); // คำนวณระยะการกระโดดVirtualProtect (( LPVOID ) pOrigMBAddress , SIZE , // กำหนดการป้องกันการอ่านเขียนPAGE_EXECUTE_READWRITE , & oldProtect ); memcpy ( oldBytes , pOrigMBAddress , SIZE ); // สร้างสำเนาสำรองmemcpy ( & JMP [ 1 ], & JMPSize , 4 ); // เติมค่า nop ด้วยระยะการกระโดด (JMP,distance(4bytes),RET) memcpy ( pOrigMBAddress , JMP , SIZE ); // ตั้งค่าคำสั่งกระโดดไว้ที่จุดเริ่มต้นของฟังก์ชันเดิมVirtualProtect (( LPVOID ) pOrigMBAddress , SIZE , oldProtect , & myProtect ); // รีเซ็ตการป้องกัน}int WINAPI MyMessageBoxW ( HWND hWnd , LPCWSTR lpText , LPCWSTR lpCaption , UINT uiType ) { VirtualProtect (( LPVOID ) pOrigMBAddress , SIZE , myProtect , & oldProtect ); // กำหนดการป้องกันการอ่านและการเขียนmemcpy ( pOrigMBAddress , oldBytes , SIZE ); // กู้คืนข้อมูลสำรองint retValue = MessageBoxW ( hWnd , lpText , lpCaption , uiType ); // รับค่าส่งคืนจากฟังก์ชันเดิมmemcpy ( pOrigMBAddress , JMP , SIZE ); // ตั้งค่าคำสั่งกระโดดอีกครั้งVirtualProtect (( LPVOID ) pOrigMBAddress , SIZE , oldProtect , & myProtect ); // รีเซ็ตการป้องกันreturn retValue ; // ส่งคืนค่าส่งคืนเดิม}ตะขอตาข่ายกรอง
ตัวอย่างนี้แสดงวิธีการใช้ hooking เพื่อเปลี่ยนแปลงการ รับส่งข้อมูล เครือข่ายในเคอร์เนล Linux โดยใช้Netfilter
#include <linux/module.h> #include <linux/kernel.h> #include <linux/skbuff.h>#include <linux/ip.h> #include <linux/tcp.h> #include <linux/in.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h>/* พอร์ตที่เราต้องการดรอปแพ็กเก็ต */ static const uint16_t port = 25 ;/* นี่คือฟังก์ชัน hook เอง */ static unsigned int hook_func ( unsigned int hooknum , struct sk_buff ** pskb , const struct net_device * in , const struct net_device * out , int ( * okfn )( struct sk_buff * )) { struct iphdr * iph = ip_hdr ( * pskb ); struct tcphdr * tcph , tcpbuf ;ถ้า( iph- > protocol != IPPROTO_TCP ) ให้คืนค่าNF_ACCEPT ;tcph = skb_header_pointer ( * pskb , ip_hdrlen ( * pskb ), sizeof ( * tcph ), & tcpbuf ); if ( tcph == NULL ) return NF_ACCEPT ;return ( tcph -> dest == port ) ? NF_DROP : NF_ACCEPT ; }/ * ใช้สำหรับลงทะเบียนฟังก์ชัน hook ของเรา * / static struct nf_hook_ops nfho = { .hook = hook_func , .hooknum = NF_IP_PRE_ROUTING , .pf = NFPROTO_IPV4 , .priority = NF_IP_PRI_FIRST , } ;static __init int my_init ( void ) { return nf_register_hook ( & nfho ); }static __exit void my_exit ( void ) { nf_unregister_hook ( & nfho ); }module_init ( my_init ); module_exit ( my_exit );การเชื่อมต่อ IAT ภายใน
โค้ดต่อไปนี้แสดงวิธีการดักจับฟังก์ชันที่นำเข้าจากโมดูลอื่น วิธีนี้สามารถใช้ดักจับฟังก์ชันในกระบวนการที่แตกต่างจากกระบวนการที่เรียกใช้ได้ สำหรับวิธีนี้ โค้ดจะต้องถูกคอมไพล์เป็น ไฟล์ DLLจากนั้นโหลดเข้าไปในกระบวนการเป้าหมายโดยใช้วิธีการฉีด DLL ใดๆ ก็ได้ ข้อดีของวิธีนี้คือ ตรวจจับได้ยากกว่าโดยโปรแกรมป้องกันไวรัสและ/หรือโปรแกรมป้องกันการโกงเราสามารถสร้างวิธีการดักจับภายนอกที่ไม่ใช้การเรียกใช้ที่เป็นอันตรายใดๆ ได้ ส่วนหัวของไฟล์Portable Executable (PExecutable) มี ตารางที่อยู่การนำเข้า ( Import Address Tableหรือ IAT) ซึ่งสามารถแก้ไขได้ดังที่แสดงในโค้ดด้านล่าง โค้ดด้านล่างทำงานบนระบบปฏิบัติการ Microsoft Windows
#include <windows.h>typedef int ( __stdcall * pMessageBoxA ) ( HWND hWnd , LPCSTR lpText , LPCSTR lpCaption , UINT uType ); // นี่คือ 'ประเภท' ของการเรียก MessageBoxA pMessageBoxA RealMessageBoxA ; // นี่จะเก็บพอยเตอร์ไปยังฟังก์ชันดั้งเดิมvoid DetourIATptr ( const char * function , void * newfunction , HMODULE module );int __stdcall NewMessageBoxA ( HWND hWnd , LPCSTR lpText , LPCSTR lpCaption , UINT uType ) { //ฟังก์ชันจำลองของเราprintf ( "ข้อความที่ส่งไปยัง MessageBoxA คือ : %s \n " , lpText ); return RealMessageBoxA ( hWnd , lpText , lpCaption , uType ); //เรียกใช้ฟังก์ชันจริง}int main ( int argc , CHAR * argv []) { DetourIATptr ( "MessageBoxA" ,( void * ) NewMessageBoxA , 0 ); // ดักจับฟังก์ชันMessageBoxA ( NULL , "Just A MessageBox" , "Just A MessageBox" , 0 ); // เรียกใช้ฟังก์ชัน -- ซึ่งจะเรียกใช้การดักจับปลอมของเราreturn 0 ; }void ** IATfind ( const char * function , HMODULE module ) { //ค้นหาข้อมูลในตารางที่อยู่สำหรับการนำเข้า (Import Address Table) ที่เฉพาะเจาะจงสำหรับฟังก์ชันที่กำหนดint ip = 0 ; if ( module == 0 ) module = GetModuleHandle ( 0 ); PIMAGE_DOS_HEADER pImgDosHeaders = ( PIMAGE_DOS_HEADER ) module ; PIMAGE_NT_HEADERS pImgNTHeaders = ( PIMAGE_NT_HEADERS )(( LPBYTE ) pImgDosHeaders + pImgDosHeaders -> e_lfanew ); PIMAGE_IMPORT_DESCRIPTOR pImgImportDesc = ( PIMAGE_IMPORT_DESCRIPTOR )(( LPBYTE ) pImgDosHeaders + pImgNTHeaders -> OptionalHeader . DataDirectory [ IMAGE_DIRECTORY_ENTRY_IMPORT ]. VirtualAddress );ถ้า( pImgDosHeaders- > e_magic != IMAGE_DOS_SIGNATURE ) printf ( "libPE Error: e_magic ไม่ใช่ลายเซ็น DOS ที่ถูกต้อง\n " );สำหรับ( IMAGE_IMPORT_DESCRIPTOR * iid = pImgImportDesc ; iid -> Name != NULL ; iid ++ ) { สำหรับ( int funcIdx = 0 ; * ( funcIdx + ( LPVOID * )( iid -> FirstThunk + ( SIZE_T ) module )) != NULL ; funcIdx ++ ) { char * modFuncName = ( char * )( * ( funcIdx + ( SIZE_T * )( iid -> OriginalFirstThunk + ( SIZE_T ) module )) + ( SIZE_T ) module + 2 ); const uintptr_t nModFuncName = ( uintptr_t ) modFuncName ; bool isString = ! ( nModFuncName & ( sizeof ( nModFuncName ) == 4 ? 0x80000000 : 0x8000000000000000 )); if ( isString ) { if ( ! _stricmp ( function , modFuncName )) return funcIdx + ( LPVOID * )( iid -> FirstThunk + ( SIZE_T ) module ); } } } return 0 ; }void DetourIATptr ( const char * function , void * newfunction , HMODULE module ) { void ** funcptr = IATfind ( function , module ); if ( * funcptr == newfunction ) return ;DWORD oldrights , newrights = PAGE_READWRITE ; // อัปเดตการป้องกันเป็น READWRITE VirtualProtect ( funcptr , sizeof ( LPVOID ), newrights , & oldrights );RealMessageBoxA = ( pMessageBoxA ) * funcptr ; //คอมไพเลอร์บางตัวต้องการการแปลงประเภท (เช่น "MinGW") แต่ไม่แน่ใจเกี่ยวกับ MSVC * funcptr = newfunction ;//กู้คืนแฟล็กการป้องกันหน่วยความจำเก่าVirtualProtect ( funcptr , sizeof ( LPVOID ), oldrights , & newrights ); }ดูเพิ่มเติม
- ฟังก์ชันเรียกกลับ (วิทยาการคอมพิวเตอร์)
- การมอบหมายงาน (การเขียนโปรแกรม)
- โปรแกรมการเลิกจ้างแต่ยังคงพักอาศัย
- ออกจากระบบ
ลิงก์ภายนอก
วินโดวส์
- ข้อมูลเกี่ยวกับการเชื่อมต่อฟังก์ชันนำเข้าตารางที่อยู่ (Import Address Table function hooking)
- ข้อมูลจาก Microsoft เกี่ยวกับการเชื่อมต่อ (hooking)
- ข้อมูลและเทคนิคต่างๆ เกี่ยวกับการเชื่อมต่อระบบด้วยสถาปัตยกรรม x86
- APISpy32เป็นแอปพลิเคชันที่ใช้ในการดักจับการทำงานของ Win32 API
- Detoursเป็นไลบรารีสำหรับดักจับฟังก์ชันอเนกประสงค์ที่สร้างโดยMicrosoft Researchซึ่งทำงานได้ในภาษา C และ C++
- winspyมีสามวิธีในการแทรกโค้ดเข้าไปในกระบวนการอื่น
- HookTool SDK (ACF SDK)ให้ภาพรวมที่ครอบคลุมเกี่ยวกับการดักจับ API และการแทรกโค้ดมีผลิตภัณฑ์เชิงพาณิชย์วางจำหน่ายด้วยเช่นกัน
- madCodeHookเป็นไลบรารีเชิงพาณิชย์สำหรับการดักจับ API และการฉีด DLL สำหรับสถาปัตยกรรม x86 และ x64 ในภาษา C++ และ Delphi
- EasyHookเป็นเอนจิ้นดักจับข้อมูลแบบโอเพนซอร์สที่รองรับสถาปัตยกรรม x86 และ x64 ในระบบปฏิบัติการ Windows ทั้งในระดับผู้ใช้และระดับเคอร์เนล
- SpyStudio Application Traceคือโปรแกรมติดตามการทำงานของแอปพลิเคชัน ซึ่งจะดักจับการเรียกใช้ฟังก์ชันและแสดงผลลัพธ์ในรูปแบบที่มีโครงสร้าง
- rohitab.com API Monitorเป็นแอปพลิเคชันฟรีแวร์ที่สามารถดักจับและแสดงผล API ของ Windows และอินเทอร์เฟซ COM มากกว่า 10,000 รายการ ในแอปพลิเคชันและบริการ 32 บิตและ 64 บิต
- Deviare API Hookคือเฟรมเวิร์ก hook ระหว่างกระบวนการแบบฟรีแวร์ ที่สามารถใช้เพื่อดักจับการเรียกใช้ API ของกระบวนการอื่น และแสดงข้อมูลพารามิเตอร์ทั้งหมด หรือใช้สร้างตัวตรวจสอบ API ได้
- WinAPIOverrideเป็นซอฟต์แวร์ฟรีสำหรับใช้ในงานที่ไม่ใช่เชิงพาณิชย์ สามารถดักจับ API ของ Win32, COM, OLE, ActiveX, .NET ในกระบวนการ 32 บิตและ 64 บิตได้ รวมถึงเครื่องมือวิเคราะห์และตรวจสอบหลังการทำงานด้วย
- urmem เป็นไลบรารี C++11 แบบข้ามแพลตฟอร์ม (x86) สำหรับการทำงานกับหน่วยความจำ (hooks, patches, pointer's wrapper, signature scanner เป็นต้น)
ลินุกซ์
- [1]โครงงานวิจัยของนักศึกษาที่ใช้เทคนิค hooking
- [2]ฟังก์ชันการทำงานที่ช่วยให้ซอฟต์แวร์สามารถสังเกตและควบคุมการทำงานของกระบวนการอื่นได้
- [3]การใช้ LD_PRELOAD เพื่อดักจับการเรียกไลบรารีที่ใช้ร่วมกัน
อีแมคส์
- Hooks ใน Emacsเป็นกลไกสำคัญสำหรับการปรับแต่ง Emacs Hook คือตัวแปร Lisp ที่เก็บรายการฟังก์ชันที่จะถูกเรียกใช้ในโอกาสที่กำหนดไว้ (เรียกว่าการเรียกใช้ Hook)
ระบบปฏิบัติการ OS X และ iOS
- Cydia Substrateคือเฟรมเวิร์กสำหรับอุปกรณ์ iOS ที่เจลเบรกแล้ว ซึ่งช่วยให้นักพัฒนาสามารถเชื่อมต่อกับเฟรมเวิร์กหรือแอปพลิเคชันอื่นๆ ได้
- Harpoonเป็น ไลบรารีสำหรับ ระบบปฏิบัติการ OS Xที่ใช้ในการดักจับฟังก์ชันขณะรันไทม์
การเชื่อมต่อ API อย่างละเอียด
- บทความ " การดักจับ API สำหรับสถาปัตยกรรม x86 แบบเข้าใจง่าย"เกี่ยวกับวิธีการดักจับ API ต่างๆ สำหรับสถาปัตยกรรม x86
สรุปเนื้อหา
ข้อมูลสำคัญจากบทความ
ข้อมูลสำคัญเกี่ยวกับ การเกี่ยว
ใน การเขียนโปรแกรมคอมพิวเตอร์ การดักจับ (hooking) คือเทคนิคต่างๆ ที่ใช้ในการเปลี่ยนแปลงหรือเสริมพฤติกรรมของ ระบบปฏิบัติการ แอ ปพลิเคชัน หรือส่วนประกอบซอฟต์แวร์อื่นๆ โดยการดักจับ...
วิธีการ
โดยทั่วไปแล้ว การแทรก hook จะทำในขณะที่ซอฟต์แวร์กำลังทำงานอยู่ แต่การแทรก hook ก็เป็นกลยุทธ์ที่สามารถใช้ได้ก่อนที่แอปพลิเคชันจะเริ่มต้นทำงานด้วยเช่นกัน เทคนิคทั้งสองนี้จะอธิบายรายละเอียดเพิ่มเติมด้านล่าง
การแก้ไขแหล่งที่มา
การดักจับการทำงาน (Hooking) สามารถทำได้โดยการแก้ไขซอร์สโค้ดของไฟล์ ปฏิบัติการ หรือ ไลบรารี ก่อนที่แอปพลิเคชันจะทำงาน โดยใช้วิธี การวิศวกรรมย้อนกลับ (Reverse Engineering ) โดยทั่วไปแล้วจะใช้เพื่อดักจับการเรียกใช้ฟังก์ชัน...
การห่อสัญลักษณ์
GNU Linker รองรับ --wrap=foo ตัวเลือก CLI เมื่อใช้งานแล้ว ตัวเชื่อมโยงจะแทนที่การเรียกใช้ทั้งหมดไปยัง foo ด้วยการเรียกใช้ไปยัง __wrap_foo คำจำกัดความดั้งเดิมของ สามารถอ้างอิงได้ ด้วย foo สัญลักษณ์ [ 1 ] เนื่องจากวิธีนี้ไม่ได้ใช้ ตัวเชื่อมโยงแบบไดนามิก...