ปลั๊กอิน ida_medigate C++ สำหรับ IDA Pro
[สารบัญ]
การทำวิศวกรรมย้อนกลับของโค้ด C++ ที่คอมไพล์แล้วนั้นไม่ใช่เรื่องสนุก วิศวกรรมย้อนกลับแบบคงที่ของโค้ด C ++ ที่คอมไพล์แล้วนั้น น่าหงุดหงิด สาเหตุหลักที่ทำให้มันยากมากคือฟังก์ชั่นเสมือนจริง ตรงกันข้ามกับโค้ด C ที่คอมไพล์แล้ว ไม่มีการไหลของโค้ดที่ชัดเจน หลายครั้งเกินไปที่เราจะใช้เวลามากมายในการพยายามทำความเข้าใจว่าฟังก์ชันเสมือนถัดไปเรียกว่าอะไร แทนที่จะเห็นฟังก์ชันเหมือนกับในโค้ด C ที่คอมไพล์แล้ว
เมื่อเราตรวจสอบฟังก์ชันเสมือน ระยะเวลาที่เขาหรือเธอต้องใช้ความพยายามเพื่อค้นหา xref นั้นไม่สมเหตุสมผล
หลังจากโปรเจ็กต์ C++ RE มากเกินไป ฉันยอมแพ้และตัดสินใจว่าต้องการเครื่องมือที่ยืดหยุ่น (Python) และเสถียร (ซึ่งฉันสามารถดูแลรักษาได้ง่าย) สำหรับการวิจัยประเภทนี้ ปลั๊กอินนี้ส่วนใหญ่เขียนขึ้นในเดือนมกราคม 2018 และเมื่อเร็ว ๆ นี้ ฉันตัดสินใจที่จะกำจัดฝุ่นและเพิ่มการรองรับคลาส IDA (7.2) ใหม่
ปลั๊กอินนี้ไม่ได้ตั้งใจให้ทำงาน "นอกกรอบ" เสมอไป แต่เพื่อเป็นอีกเครื่องมือหนึ่งสำหรับผู้กลับทาง
ปลั๊กอินประกอบด้วยสองส่วน:
ส่วนแรกนี้ไม่ขึ้นอยู่กับส่วนที่สอง ดังนั้นจึงเป็นไปได้ที่จะใช้ปลั๊กอินเพื่อย้อนกลับวิศวกรรมไบนารีที่ไม่มี RTTI โดยการกำหนดคลาสเหล่านั้นด้วยตนเองตาม API ของปลั๊กอิน
สิ่งที่ทำให้ปลั๊กอินมีเอกลักษณ์เฉพาะตัวคือความจริงที่ว่าปลั๊กอินใช้สภาพแวดล้อมเดียวกันกับที่ผู้วิจัยคุ้นเคยอยู่แล้ว และไม่เพิ่มเมนูหรือวัตถุใหม่ใดๆ และอิงตามหน่วยการสร้าง IDA ที่รู้จัก (โครงสร้าง สหภาพ ประเภทสำหรับสมาชิกของโครงสร้าง ฯลฯ ) - เปิดใช้งานปลั๊กอินเพื่อรองรับบทคัดย่อ C++ สำหรับทุกสถาปัตยกรรมที่ IDA รองรับ
หมายเหตุ: ตัวแยกวิเคราะห์ RTTI แยกวิเคราะห์ RTTI x86/x64 g++ แต่โครงสร้างทำให้สามารถเพิ่มการรองรับสถาปัตยกรรมและคอมไพเลอร์ได้มากขึ้น อย่างง่ายดาย
plugins/
) คัดลอก medigate_cpp_plugin
ไปยังไดเร็กทอรี plugins
และเพิ่มเส้นทางซอร์สโค้ดลงในไฟล์ idapythonrc.py
ของคุณ
คัดลอก plugins/ida-referee/referee.py
ไปยังไดเร็กทอรีเดียวกัน
สมมติว่าซอร์สโค้ดต้นฉบับไบนารีมีดังต่อไปนี้ ( examples/a.cpp
):
using namespace std ;
class A {
public:
int x_a;
virtual int f_a ()=0;
};
class B : public A {
public:
int x_b;
int f_a (){x_a = 0 ;}
virtual int f_b (){ this -> f_a ();}
};
class Z {
public:
virtual int f_z1 (){cout << " f_z1 " ;}
virtual int f_z2 (){cout << " f_z2 " ;}
};
class C : public B , public Z {
public:
int f_a (){x_a = 5 ;}
int x_c;
int f_c (){x_c = 0 ;}
virtual int f_z1 (){cout << " f_z3 " ;}
};
int main ()
{
C *c = new C ();
c-> f_a ();
c-> f_b ();
c-> f_z1 ();
c-> f_z2 ();
return 0 ;
}
ไบนารีถูกถอดออก แต่มี RTTI
เมื่อเราโหลดไบนารี่ ฟังก์ชัน main
( sub_84D
ในเวอร์ชัน 32 บิต) จะมีลักษณะดังนี้:
เริ่มต้นตัวแยกวิเคราะห์ g++ RTTI และรันโดยใช้:
from ida_medigate.rtti_parser import GccRTTIParser
GccRTTIParser.init_parser()
GccRTTIParser.build_all()
ตอนนี้รีเฟรช struct C (ดูหัวข้อหมายเหตุ) โยน v0
ให้เป็น C *
ถอดรหัสอีกครั้ง:
สำหรับกรณีที่ไม่มี RTTI โครงสร้างพื้นฐานของเรายังคงเปิดใช้งานเพื่อกำหนดคลาส c++ ด้วยตนเอง สำหรับตัวอย่างเดียวกัน (examples/a32_stripped) คุณสามารถสร้าง struct B ด้วยตนเอง จากนั้นเลือกเป็นตารางเสมือนแล้วพิมพ์
from ida_medigate import cpp_utils
cpp_utils.make_vtable("B")
make_vtable
สามารถรับ vtable_ea
และ vtable_ea_stop
แทนพื้นที่ที่เลือกได้
จากนั้นสร้าง struct C และใช้การสืบทอด:
cpp_utils.add_baseclass("C", "B")
ตอนนี้คุณสามารถสร้างคลาส C vtable ใหม่ได้โดยเลือกและพิมพ์:
cpp_utils.make_vtable("C")
เพิ่มโครงสร้าง Z สร้าง vtable ขึ้นมาใหม่ด้วย และตอนนี้เป็นส่วนที่เจ๋ง:
cpp_utils.add_baseclass("C", "Z", 0x0c, to_update=True)
ซึ่งใช้การสืบทอด C ของ Z ที่ออฟเซ็ต 0x0c และรีเฟรชโครงสร้างด้วย (ดูหมายเหตุ)
สิ่งสุดท้ายที่เหลืออยู่คืออัปเดต vtable ที่สองของ C ซึ่งเป็นอันที่ใช้อินเทอร์เฟซของ Z ทำเครื่องหมาย vtable นี้แล้วพิมพ์:
cpp_utils.make_vtable("C", offset_in_class=0x0c)
ida_medigate รู้ว่า vtable นี้เป็น vtable ของคลาส Z และผลลัพธ์จะเป็น:
ผลลัพธ์สุดท้ายจะเหมือนกับในกรณี RTTI:
เมื่อมีการสร้างโครงสร้าง vtable ใหม่ (โดยตัวแยกวิเคราะห์ RTTI ด้วยตนเองโดยผู้ใช้) แต่ละฟังก์ชันที่ยังไม่มีการเปลี่ยนแปลงจะถูกเปลี่ยนชื่อ แยกส่วน และตั้งค่าอาร์กิวเมนต์แรกเป็น this
ดับเบิลคลิกที่สมาชิกของโครงสร้างซึ่งสอดคล้องกับฟังก์ชันดังกล่าวจะนำทางไปยังฟังก์ชัน เพื่อให้ผู้ใช้สามารถอ่านโฟลว์โค้ด C++ ได้อย่างสะดวก!
การเปลี่ยนชื่อหรือประเภทของฟังก์ชันทุกครั้งหรือสมาชิกตัวชี้ฟังก์ชันที่สอดคล้องกันใน vtables จะถูกเชื่อมต่อและซิงโครไนซ์ระหว่างฟังก์ชันทั้งหมด ซึ่งหมายความว่า ตัวอย่างเช่น ผู้ใช้สามารถเปลี่ยนประเภทสมาชิก vtable ผ่านหน้าต่างตัวถอดรหัส และประเภทใหม่นี้ (ต้นแบบ) จะถูกนำไปใช้กับฟังก์ชันเป้าหมายด้วย
ในบรรทัดที่ 15 ของรูปภาพก่อนหน้า มีการเรียกไปยัง B::sub_9A8 (B::f_b ในซอร์สโค้ด) อาร์กิวเมนต์ของฟังก์ชันนี้คือ B *
:
แต่ฟังก์ชันนี้อาจถูกเรียกใช้โดยอินสแตนซ์ C
(up-casting) เราต้องการเห็นฟังก์ชันเสมือนที่อินสแตนซ์จะเรียกใช้ สมมติว่ามีคลาสที่ได้รับที่เป็นไปได้มากมาย ดังนั้นการส่ง this
ไปที่ C *
ไม่สามารถทำได้เสมอไป ด้วยเหตุผลดังกล่าว เราจึงใช้การรวมกลุ่มสำหรับคลาสพื้นฐานแต่ละคลาสที่มีบุตรชายซึ่งมีตารางเสมือนที่แตกต่างกัน เราสามารถเลือกที่จะแสดงตารางเสมือนที่ได้รับมาที่แตกต่างกันของอนุพันธ์ของ B
ได้โดยคลิก alt+y (ทางลัดสำหรับการเลือกสมาชิกสหภาพอื่น):
ในที่สุดเราก็สามารถ "ส่ง" เฉพาะการโทรเฉพาะไปยังฟังก์ชันเสมือนที่แตกต่างกันได้:
จอกศักดิ์สิทธิ์ของวิศวกรย้อนกลับ C++ ที่ผิดหวัง เรารักษา xrefs จากฟังก์ชันเสมือนไปจนถึงสมาชิกของ vtable struct ซึ่งเป็นตัวแทนของพวกเขา!
เมื่อรวมสิ่งนี้เข้ากับ ida-referee
ทำให้เราสามารถติดตาม xrefs ทั้งหมดของการโทรฟังก์ชันเสมือนได้!
ข้อจำกัด: เราสามารถติดตามได้เฉพาะการโทรเสมือนที่ได้รับการถอดรหัสแล้วเท่านั้น โชคดีที่การวิเคราะห์อัตโนมัติรู้ว่าต้องเติมประเภทอาร์กิวเมนต์ระหว่างฟังก์ชัน ดังนั้นด้วยกระบวนการวนซ้ำของการส่งอาร์กิวเมนต์เพิ่มเติม -> ถอดรหัสฟังก์ชันที่เกี่ยวข้องทั้งหมด -> อ่านโค้ดอีกครั้งและส่งอาร์กิวเมนต์เพิ่มเติม (...) ความสามารถนี้กลายเป็นจริง ๆ ทรงพลัง!
วิธีที่เราทำเครื่องหมายสมาชิกโครงสร้างเป็นคลาสย่อยใน IDAPython จะไม่ซิงโครไนซ์กับ IDB ทันที แฮ็คที่เราทำคือแก้ไขโครงสร้างเพื่อให้การซิงโครไนซ์เกิดขึ้น คุณยังอาจใช้
utils.refresh_struct(struct_ptr)
ซึ่งเพิ่มฟิลด์จำลองที่ส่วนท้ายของโครงสร้างแล้วไม่ได้กำหนดไว้