Chương 20: Phân Tích C++ trong Malware


1. Tổng Quan

Phân tích malware viết bằng C++ khó hơn C thuần vì C++ có nhiều cấu trúc đặc thù ảnh hưởng trực tiếp đến assembly. Các khái niệm cần nắm: OOP, this pointer, name mangling, vtable, constructor/destructor.


2. Lập Trình Hướng Đối Tượng (OOP)

C++ tổ chức code theo class — tương tự struct trong C nhưng chứa thêm hàm (method). Một class là bản thiết kế; khi chạy, ta tạo ra instance (object) từ class đó.

class SimpleClass {
public:
    int x;
    void HelloWorld() {
        printf("Hello World\n");
    }
};

int _tmain(int argc, _TCHAR* argv[]) {
    SimpleClass myObject;
    myObject.HelloWorld();
}

Lưu ý phân tích: public là cơ chế kiểm soát truy cập của compiler — không ảnh hưởng gì đến assembly.


3. Con Trỏ this

Khái niệm

Khi một method được gọi, nó cần biết đang thao tác trên object nào. this là con trỏ ẩn trỏ đến object hiện tại, được truyền ngầm vào mọi method call.

class SimpleClass {
public:
    int x;
    void HelloWorld() {
        if (x == 10) printf("X is 10.\n"); // x ngầm là this->x
    }
};

int _tmain(...) {
    SimpleClass myObject;
    myObject.x = 9;
    myObject.HelloWorld(); // this = &myObject

    SimpleClass myOtherObject;
    myOtherObject.x = 10;
    myOtherObject.HelloWorld(); // this = &myOtherObject → in ra "X is 10."
}

Trong Assembly (Microsoft compiler)

this thường được truyền qua register ECX (đôi khi ESI). Calling convention này gọi là thiscall.

; Main Function
00401110  mov [ebp+var_C], 9          ; myObject.x = 9
00401117  lea ecx, [ebp+var_10]       ; ECX = &myObject (this pointer)
0040111A  call sub_4115D0             ; gọi HelloWorld

; HelloWorld Function
004115DC  mov [ebp+var_4], ecx        ; lưu this vào stack
004115DF  mov eax, [ebp+var_4]        ; eax = this
004115E2  cmp dword ptr [eax+4], 0Ah  ; so sánh this->x với 10
004115E6  jnz short loc_4115F6
004115E8  push offset aXIs10_         ; "X is 10.\n"
004115ED  call ds:__imp__printf

4. Overloading & Name Mangling

Function Overloading

C++ cho phép nhiều hàm cùng tên nhưng khác tham số. Compiler tự chọn hàm phù hợp tại compile time.

LoadFile(String filename) { ... }
LoadFile(String filename, int Options) { ... }

Main() {
    LoadFile("c:\\myfile.txt");              // → gọi hàm 1
    LoadFile("c:\\myfile.txt", GENERIC_READ); // → gọi hàm 2
}

Name Mangling

PE format chỉ lưu tên hàm, không lưu signature. Để phân biệt các overload, compiler mã hóa thông tin tham số vào tên hàm.

Hàm gốcTên sau mangling
SimpleClass::TestFunction(int, int)?TestFunction@SimpleClass@@QAEXHH@Z

5. Kế Thừa (Inheritance)

Child class tự động có toàn bộ method và data của parent class.

class Socket {
public:
    void setDestinationAddr(INetAddr* addr) { ... }
};

class UDPSocket : public Socket {
public:
    void sendData(char* buf, INetAddr* addr) {
        setDestinationAddr(addr); // gọi method của parent
        // ...
    }
};

6. Virtual Functions & Vtable

Virtual vs Non-virtual

Non-virtualVirtual
Quyết định hàm nào gọiCompile timeRuntime
Kết quả ví dụIn “Class A”In “Class B”
Cấu trúc hỗ trợKhông cầnVtable
// Non-virtual → luôn gọi A::foo dù object thực là B
class A { public: void foo() { printf("Class A\n"); } };
class B : public A { public: void foo() { printf("Class B\n"); } };

// Virtual → gọi đúng hàm theo object thực tại runtime
class A { public: virtual void foo() { printf("Class A\n"); } };
class B : public A { public: virtual void foo() { printf("Class B\n"); } };

void g(A& arg) { arg.foo(); }

int main() {
    B b;
    g(b); // non-virtual → "Class A"; virtual → "Class B"
}

Assembly So Sánh

; NON-VIRTUAL: địa chỉ hàm cố định tại compile time
00401003  mov ecx, [ebp+arg_0]
00401006  call sub_401030          ; địa chỉ rõ ràng

; VIRTUAL: địa chỉ hàm tra qua vtable tại runtime
00401003  mov eax, [ebp+arg_0]    ; eax = con trỏ object
00401006  mov edx, [eax]          ; edx = vtable pointer (4 byte đầu của object)
00401008  mov ecx, [ebp+arg_0]    ; ecx = this
0040100B  mov eax, [edx]          ; eax = địa chỉ hàm đầu tiên trong vtable
0040100D  call eax                ; gọi gián tiếp → không biết đích trực tiếp!

Cấu Trúc Vtable

graph LR OBJ["Object\n[vtable_ptr][data1][data2]"] -->|"4 byte đầu"| VT["Vtable\n[&func1]\n[&func2]\n[&func3]"] VT --> F1["Function 1\npush ebp\nmov ebp,esp\n..."] VT --> F2["Function 2\n..."]
  • 4 byte đầu của mỗi object chứa virtual functions → là con trỏ đến vtable
  • Vtable là mảng function pointer, mỗi virtual function chiếm 1 entry 4 byte
  • Mỗi class có vtable riêng

Nhận Diện Vtable trong IDA Pro

; Vtable của class với 3 virtual functions
004020F0  off_4020F0  dd offset sub_4010A0   ; virtual func 1
004020F4              dd offset sub_4010C0   ; virtual func 2
004020F8              dd offset sub_4010E0   ; virtual func 3

Phát Hiện Quan Hệ Kế Thừa qua Vtable

; Vtable class CON (lớn hơn → đây là child)
004020DC  off_4020DC  dd offset sub_401100   ; riêng của child
004020E0              dd offset sub_4010C0   ; riêng của child
004020E4              dd offset sub_4010E0    ; CHUNG với parent
004020E8              dd offset sub_401120   ; riêng của child
004020EC              dd offset unk_402198

; Vtable class CHA (nhỏ hơn → đây là parent)
004020F0  off_4020F0  dd offset sub_4010A0
004020F4              dd offset sub_4010C0
004020F8              dd offset sub_4010E0    ; CHUNG với child

7. Constructor & Destructor

ConstructorDestructor
Khi nào gọiLúc object được tạoLúc object bị hủy
Mục đíchKhởi tạo dữ liệuDọn dẹp tài nguyên

Object trên Stack vs Heap

  • Stack: Compiler tự cấp phát, không cần new
  • Heap: Dùng toán tử new → gọi operator new + constructor
; new operator trong IDA Pro
0040108B  push 4
0040108D  call ??2@YAPAXI@Z     ; operator new(uint) — IDA tự nhận diện
; Compiler gán vtable vào object vừa tạo
00401076  mov [ebp+var_10], offset off_4020F0  ; vtable parent (bị ghi đè)
0040107D  mov [ebp+var_10], offset off_4020DC  ; vtable child (vtable thực sự)

8. Tổng Kết — Checklist Phân Tích C++ Malware

✅ Thấy LEA ECX + CALL → thiscall → có object, tìm class
✅ Thấy CALL EAX/EDX → virtual call → truy vtable tìm đích thực
✅ Thấy dãy dd offset sub_XXXX → đây là vtable
✅ Vtable lớn hơn → child class
✅ Function xuất hiện 2 vtable → kế thừa từ parent
✅ operator new (??2@YAPAXI@Z) → object heap-allocated, vtable gần đó
✅ Mangled name trong import/export → IDA demangle để đọc