ชุดโปรแกรมที่ซับซ้อนมากขึ้นเรื่อยๆ ซึ่งสาธิตการเชื่อมต่อฟังก์ชันบน Windows 64 บิต
ฉันเขียนโปรแกรมเหล่านี้พร้อมกับสอนตัวเองว่าฟังก์ชั่น hooking ทำงานอย่างไร อาจเป็นประโยชน์สำหรับคนอื่นๆ ที่พยายามทำสิ่งเดียวกัน (หรืออนาคตฉันหลังจากที่ฉันลืมว่าทั้งหมดนี้ทำงานอย่างไรอีกครั้ง) มีไว้ให้ดูตามลำดับ ครั้งแรกที่มีการใช้ฟังก์ชันในโปรแกรม ฟังก์ชั่นนั้นจะถูกรวมไว้ในไฟล์ .cpp สำหรับโปรแกรมตัวอย่างนั้นๆ โดยมีชื่อนำหน้าด้วยเครื่องหมายขีดล่าง ตัวอย่างต่อมาที่ใช้ฟังก์ชันเดียวกันจะใช้สำเนาของฟังก์ชันนั้นที่รวมอยู่ใน hooking_common.h เพื่อลดความซ้ำซ้อนของโค้ด และทำให้โปรแกรมตัวอย่างในภายหลังมีขนาดเล็กพอที่จะอ่านได้ง่าย
ฉันได้ทำงานเล็กน้อยเพื่อทำความสะอาดพวกมัน แต่ตัวอย่างต่อมายังคงยุ่งอยู่เล็กน้อย สิ่งสำคัญคือต้องทราบว่าผลลัพธ์สุดท้ายใน repo นี้ไม่เพียงพอที่จะเขียนไลบรารี hooking ที่มีคุณลักษณะครบถ้วน แต่ก็เพียงพอที่จะเริ่มต้นในเส้นทางนั้น
ขณะรันไทม์ บางโปรเจ็กต์อาจต้องอาศัยโปรเจ็กต์อื่นที่ถูกสร้างขึ้น (การแทรก dll จำเป็นต้องมี dll ที่ถูกสร้างขึ้น) หรือการทำงานในเวลาเดียวกัน (การเชื่อมต่อกับโปรแกรมเป้าหมายจำเป็นต้องให้โปรแกรมทำงานอยู่แล้ว)
ตัวอย่างทั้งหมดสร้างขึ้นโดยใช้ Visual Studio 2019 (v142) พร้อม Windows SDK 10.0.17763.0 ฉันไม่คิดว่ามีอะไรที่นี่ที่ขึ้นอยู่กับเวอร์ชัน VS หรือ SDK แต่ฉันแสดงรายการไว้ที่นี่เผื่อไว้ มีบางสิ่งที่เฉพาะเจาะจงกับ MSVC เกือบจะแน่นอน
ในที่สุด ตัวอย่างแทรมโพลีนสุดท้ายจะติดตั้งตะขอใน mspaint ฉันคิดว่าในอนาคตการอัปเดต mspaint จะทำให้ตัวอย่างนี้เสียหาย ในขณะที่เขียน mspaint เวอร์ชันปัจจุบันคือ 1909 (OS Build 18363.1016)
ตัวอย่างแบ่งออกเป็นสองประเภท: ประเภทที่ใช้แทรมโพลีน และประเภทที่ไม่ใช้ ตัวอย่างที่ไม่ใช่แทรมโพลีนมีไว้เพื่อแสดงการเปลี่ยนเส้นทางการไหลของโปรแกรมจากฟังก์ชันหนึ่งไปยังอีกฟังก์ชันหนึ่งในสถานการณ์ที่แตกต่างกันเท่านั้น การสร้างแทรมโพลีนนั้นซับซ้อน และเมื่อฉันพยายามค้นหาว่าฟังก์ชันการเชื่อมต่อทำงานอย่างไร การเริ่มต้นด้วยการสร้างตัวอย่างที่ไม่ใช่แทรมโพลีนก่อนจะเป็นประโยชน์อย่างมาก นอกจากนี้ยังมี "โปรแกรมเป้าหมาย" 4 โปรแกรมซึ่งใช้โดยตัวอย่างที่ต้องการสาธิตวิธีการติดตั้ง hooks ในกระบวนการต่างๆ (กำลังทำงานอยู่แล้ว)
ตัวอย่างเหล่านี้ส่วนใหญ่รั่วไหลของหน่วยความจำที่เกี่ยวข้องกับ hooks ฉันไม่สนใจจริงๆ ทั้งคู่เพราะตัวอย่างเหล่านี้เป็นเพียงการแสดงให้เห็นถึงแนวคิดที่เกี่ยวโยงกัน และเนื่องจากการจัดสรรที่ "รั่วไหล" เหล่านี้จำเป็นต้องมีอยู่จนกว่าโปรแกรมจะยุติลง
แม้ว่าดูเหมือนจะไม่มีคำศัพท์มาตรฐานสำหรับเทคนิคการเชื่อมต่อฟังก์ชันมากนัก แต่โค้ด (และ readmes) ในพื้นที่เก็บข้อมูลนี้ใช้คำศัพท์ต่อไปนี้:
เนื่องจากตัวอย่างเหล่านี้ไม่ได้สร้างแทรมโพลีนเมื่อติดตั้งตะขอ ฉันคิดว่าฟังก์ชันเหล่านี้แสดงให้เห็นถึงการเกี่ยวแบบ "ทำลายล้าง" โดยที่ฟังก์ชันดั้งเดิมจะใช้งานไม่ได้โดยสิ้นเชิงหลังจากถูกตะขอ
ตัวอย่างเล็กๆ ของการเขียนทับไบต์เริ่มต้นของฟังก์ชันด้วยคำสั่ง Jump ที่เปลี่ยนเส้นทางการไหลของโปรแกรมไปยังฟังก์ชันอื่นภายในโปรแกรมเดียวกัน เนื่องจากไม่มีการสร้างแทรมโพลีน การดำเนินการนี้จึงเป็นการทำลายล้าง และฟังก์ชันเดิมไม่สามารถเรียกใช้ได้อีกต่อไป นี่เป็นตัวอย่าง 32 บิตเพียงตัวอย่างเดียวในที่เก็บ
ตัวอย่างก่อนหน้านี้เวอร์ชัน 64 บิต ในแอปพลิเคชัน 64 บิต ฟังก์ชันสามารถอยู่ห่างจากหน่วยความจำมากพอจนไม่สามารถเข้าถึงได้ผ่านคำสั่งการข้ามแบบสัมพันธ์ 32 บิต เนื่องจากไม่มีคำสั่งการข้ามแบบสัมพันธ์ 64 บิต อันดับแรกโปรแกรมนี้จะสร้างฟังก์ชัน "รีเลย์" ซึ่งมีไบต์สำหรับคำสั่ง jmp สัมบูรณ์ที่สามารถเข้าถึงได้ทุกที่ในหน่วยความจำ (และข้ามไปที่ payload func) การกระโดดแบบ 32 บิตที่ได้รับการติดตั้งในฟังก์ชันเป้าหมายจะข้ามไปที่ฟังก์ชันรีเลย์นี้ แทนที่จะไปที่เพย์โหลดทันที
จัดเตรียมตัวอย่างการใช้เทคนิคจากโปรเจ็กต์ก่อนหน้าเพื่อเชื่อมโยงฟังก์ชันสมาชิก แทนที่จะเป็นฟังก์ชันอิสระ
แตกต่างจากตัวอย่างก่อนหน้านี้เล็กน้อย โปรแกรมนี้แสดงวิธีติดตั้ง hook ลงในฟังก์ชันสมาชิกเสมือนโดยรับที่อยู่ของฟังก์ชันนั้นผ่าน vtable ของอ็อบเจ็กต์ ไม่มีตัวอย่างอื่นใดที่เกี่ยวข้องกับฟังก์ชันเสมือน แต่ฉันคิดว่ามันน่าสนใจพอที่จะรวมไว้ที่นี่
ตัวอย่างที่ง่ายที่สุดของการติดตั้ง hook ลงในกระบวนการอื่นที่ทำงานอยู่ ตัวอย่างนี้ใช้ไลบรารี DbgHelp เพื่อค้นหาฟังก์ชันในกระบวนการเป้าหมาย (A - Target With Free Function) ตามชื่อสตริง สิ่งนี้เป็นไปได้เนื่องจากโปรแกรมเป้าหมายถูกสร้างขึ้นโดยเปิดใช้งานสัญลักษณ์การดีบัก แม้ว่าตัวอย่างนี้จะดูเรียบง่าย แต่ตัวอย่างนี้จะยาวกว่าโปรแกรมก่อนหน้านี้เล็กน้อย เนื่องจากมีฟังก์ชันใหม่จำนวนมากที่โปรแกรมแนะนำ (สำหรับการค้นหาและจัดการกระบวนการระยะไกล)
ตัวอย่างนี้แสดงวิธีการขอฟังก์ชันที่กระบวนการอื่นนำเข้าจาก dll มีความแตกต่างเล็กน้อยในการรับที่อยู่ของฟังก์ชัน dll ในกระบวนการระยะไกลเนื่องจากวิธีการทำงานของ ASLR ซึ่งแสดงไว้ที่นี่ มิฉะนั้น ตัวอย่างนี้เกือบจะเหมือนกับตัวอย่างก่อนหน้า
ตัวอย่างนี้แสดงวิธีการติดตั้ง hook ในฟังก์ชันที่ไม่ได้นำเข้าโดย dll และไม่ได้อยู่ในตารางสัญลักษณ์ (อาจเป็นเพราะกระบวนการระยะไกลไม่มีสัญลักษณ์การดีบัก) ซึ่งหมายความว่าไม่มีวิธี (ง่าย) ในการค้นหาฟังก์ชันเป้าหมายด้วยชื่อสตริง แต่ตัวอย่างนี้จะถือว่าคุณได้ใช้ตัวแยกส่วนเช่น x64dbg เพื่อรับที่อยู่เสมือนแบบสัมพัทธ์ (RVA) ของฟังก์ชันที่คุณต้องการเชื่อมต่อ โปรแกรมนี้ใช้ RVA นั้นเพื่อติดตั้ง hook
คล้ายกับที่กล่าวมาข้างต้น ยกเว้นตัวอย่างนี้ใช้การแทรก dll เพื่อติดตั้งฟังก์ชันเพย์โหลด แทนที่จะเขียนไบต์ของโค้ดเครื่องดิบ วิธีนี้ใช้งานได้ง่ายกว่ามาก เนื่องจากเพย์โหลดของคุณสามารถเขียนเป็นภาษา C++ ได้อีกครั้ง เพย์โหลดสำหรับตัวอย่างนี้มีอยู่ในโปรเจ็กต์ 08B-DLL-Payload
ตัวอย่างต่อไปนี้จะติดตั้งแทรมโพลีนเมื่อทำการเกี่ยว ซึ่งหมายความว่าโปรแกรมยังคงสามารถดำเนินการตรรกะในฟังก์ชันเป้าหมายได้หลังจากติดตั้งฮุกแล้ว เนื่องจากการติดตั้ง hook จะเขียนทับอย่างน้อย 5 ไบต์แรกในฟังก์ชันเป้าหมาย คำแนะนำที่อยู่ใน 5 ไบต์เหล่านี้จึงถูกย้ายไปยังฟังก์ชันแทรมโพลีน ดังนั้นการเรียกฟังก์ชันแทรมโพลีนจะเป็นการดำเนินการลอจิกดั้งเดิมของฟังก์ชันเป้าหมายอย่างมีประสิทธิภาพ
การติดตั้งแทรมโพลีนเทียบเท่ากับตัวอย่างที่ 2 ตัวอย่างนี้ค่อนข้างแปลกเพราะฉันต้องการสาธิตการสร้างแทรมโพลีนโดยไม่ต้องใช้เครื่องถอดชิ้นส่วน ในกรณีนี้ ฟังก์ชันเป้าหมายถูกสร้างขึ้นเพื่อให้มีคำสั่ง 5 ไบต์ที่รู้จักในตอนเริ่มต้น ดังนั้นเราจึงสามารถคัดลอกห้าไบต์แรกของฟังก์ชันนั้นไปยังฟังก์ชันแทรมโพลีนได้ ซึ่งหมายความว่าการสร้างแทรมโพลีนนั้นง่ายมาก เนื่องจากเรารู้ว่ามันมีขนาดที่แน่นอน และไม่ใช้การกำหนดที่อยู่แบบสัมพันธ์ใดๆ ที่ต้องแก้ไข หากคุณกำลังเขียนแทรมโพลีนสำหรับกรณีการใช้งานเฉพาะเจาะจง คุณอาจไม่ต้องเปลี่ยนเรื่องนี้
ตัวอย่างนี้แสดงสถานการณ์ที่คล้ายคลึงกับสถานการณ์ก่อนหน้า ยกเว้นคราวนี้ฉันใช้ตัวถอดประกอบ (capstone) เพื่อรับไบต์ที่เราต้องขโมยออกจากฟังก์ชันเป้าหมาย ซึ่งช่วยให้สามารถใช้โค้ด hooking กับฟังก์ชันใดๆ ก็ได้ ไม่ใช่แค่ฟังก์ชันที่เรารู้ว่าจะเป็นกรณีง่ายๆ จริงๆ แล้วมีหลายสิ่งหลายอย่างที่เกิดขึ้นในตัวอย่างนี้ เพราะมันกระโดดจาก hook ที่ตรงเป้าหมาย (เหมือนอันที่แล้ว) ไปสู่การสร้างฟังก์ชัน hooking ทั่วไป แทรมโพลีนต้องแปลงการโทร/การกระโดดแบบสัมพัทธ์ให้เป็นคำสั่งที่ใช้ที่อยู่ที่แน่นอน ซึ่งจะทำให้สิ่งต่างๆ ซับซ้อนยิ่งขึ้น นี่ไม่ใช่ตัวอย่างที่สมบูรณ์ 100% ของการเชื่อมต่อแบบทั่วไป แต่จะล้มเหลวด้วยคำสั่งแบบวนซ้ำ และหากคุณพยายามเชื่อมต่อฟังก์ชันด้วยคำสั่งน้อยกว่า 5 ไบต์
โดยพื้นฐานแล้วจะเหมือนกับข้างต้น ยกเว้นตัวอย่างนี้มีโค้ดเพื่อหยุดเธรดที่ดำเนินการทั้งหมดชั่วคราวในขณะที่ติดตั้ง hook การดำเนินการนี้ไม่รับประกันว่าจะปลอดภัยสำหรับเธรดในทุกกรณี แต่จะปลอดภัยกว่าการไม่ทำอะไรเลยอย่างแน่นอน
สิ่งนี้จะขยายโค้ด hooking/trampoline ที่ใช้ในสองตัวอย่างก่อนหน้านี้ เพื่อรองรับการเปลี่ยนเส้นทางหลายฟังก์ชันไปยังเพย์โหลดเดียวกัน และเพื่อให้ฟังก์ชันเพย์โหลดเรียกใช้ฟังก์ชันอื่นที่มี hooks ติดตั้งอยู่
นี่เป็นตัวอย่างแทรมโพลีนตัวแรกที่ติดตั้ง hook ในกระบวนการอื่น (ในกรณีนี้คือแอปเป้าหมาย B - เป้าหมายที่มีฟังก์ชันอิสระจาก DLL) ตรรกะการเชื่อมต่อทั้งหมดมีอยู่ในเพย์โหลด dll 13B - Trampoline Imported Func DLL Payload ไม่มีอะไรใหม่มากนักที่นี่ ตัวอย่างนี้เป็นเพียงการรวมสิ่งที่ทำไปแล้วของแทรมโพลีน hooking เข้ากับเทคนิคที่แสดงไว้ก่อนหน้านี้สำหรับการเชื่อมต่อฟังก์ชันที่นำเข้าจาก dll
มงกุฎเพชรแห่งการซื้อคืน ตัวอย่างนี้แทรกเพย์โหลด dll (14B - Trampoline Hook MSPaint Payload) ลงในอินสแตนซ์ที่รันอยู่ของ mspaint (คุณต้องเปิด mspaint ด้วยตัวเองก่อนที่จะรัน) ตะขอที่ติดตั้งจะทำให้แปรงวาดเป็นสีแดง ไม่ว่าคุณจะเลือกสีอะไรใน MSPaint ก็ตาม จริงๆ แล้วไม่มีอะไรที่นี่ที่ไม่ได้แสดงในตัวอย่างก่อนหน้านี้ มันเจ๋งมากที่เห็นว่าสิ่งนี้ใช้ได้กับโปรแกรมที่ไม่ได้ถูกคิดค้นขึ้นมา
แอปพลิเคชันเป้าหมายอย่างง่ายที่เรียกใช้ฟังก์ชันฟรีในลูป รวบรวมพร้อมข้อมูลการแก้ไขข้อบกพร่องรวมอยู่ด้วย
กำหนดเป้าหมายแอปพลิเคชันที่เรียกใช้ฟังก์ชันฟรีที่นำเข้าจาก dll (B2 - GetNum-DLL) ในลูป
กำหนดเป้าหมายแอปพลิเคชันที่เรียกใช้ฟังก์ชันที่ไม่ใช่สมาชิกเสมือนในลูป
กำหนดเป้าหมายแอปพลิเคชันที่เรียกใช้ฟังก์ชันสมาชิกเสมือนในลูป