## Reflective DLL Injection
### Tổng quan
Reflective DLL Injection (RDI) là một kỹ thuật được phát triển bởi Stephen Fewer, tuy nhiên nền tảng của kỹ thuật này là Manaul Map (MM) có từ những cuối những năm 90. RDI là kỹ thuật để tạo và load một DLL vào bộ nhớ để tránh việc sử dụng đến disk. Kỹ thuật này sẽ hữu ích trong việc phát triển mã độc để các AV/EDR khó phát hiện hơn trừ khi kiểm tra sâu vào bộ nhớ.
Vậy kỹ thuật này được thực hiện như thế nào? Như đã biết, khi tải một DLL vào trong Windows ta cần gọi đến LoadLibrary API để lấy đường dẫn của DLL và load nó vào trong bộ nhớ. Tuy nhiên, trong kỹ thuật này chúng ta cố gắng không tải từ disk mà thay vào đó sẽ load từ bộ nhớ. LoadLibrary lại không thực hiện được việc này nên ta sẽ tự tạo ra một hàm LoadLibrary riêng. Như vậy về cơ bản thì để thực hiển RDI ta cần load và thực thi DLL ở trong bộ nhớ.

## Các bước thực hiện RDI
### Xử lý địa chỉ của API trong bộ nhớ
Khi một PE được load vào disk thì tất cả các API được gọi có thể tìm thấy trong Import Address Table (IAT) và Export Address Table (EAT). Các bảng này được tạo bởi linker trong thời gian chạy. Tuy nhiên, chúng ta không lấy được địa chỉ của các API trong bảng này nên sẽ phải tự tạo ra các API này một cách thủ công.
Chúng ta có thể truy vấn các API này bằng việc sử dụng Process Environment Block (PEB). Trong PEB có rất nhiều cấu trúc chứa thông tin về tiến trình đang chạy nhưng ta chỉ quan tâm đến cấu trúc Ldr.
Để truy cập được vào PEB, chúng ta có thể truy cập thông qua cấu trúc chứa nó được gọi là Thread Environment Block (TEB). TEB được truy cập thông qua các thanh ghi trong assembly (với kiến trúc 32 bit thì sẽ là thanh ghi FS và với kiến trúc 64 bit là thanh ghi GS). Các offset cho các thanh ghi trên lần lượt là 0x30 (32bit) và 0x60 (64bit).

Bây giờ trong cấu trúc Ldr sẽ cung cấp cho chúng ta tên của các DLL được tải trong bộ nhớ của tiến trình đang chạy. Chúng ta cũng sẽ kiểm tra xem DLL trong danh sách có phải là DLL mà ta cần cho việc lấy API của mình không.

Cốt lõi của đoạn code trên là truy cập vào danh sách liên kết trong cấu trúc Ldr được gọi là InLoadOrderModuleList. Sau đó nó sẽ lặp để lấy thông tin của module hiện tại từ cấu trúc LDR_DATA_TABLE_ENTRY và kiểm tra xem có khớp với module mà ta yêu cầu không. Nếu tìm thấy nó sẽ trả về địa chỉ cơ sở của module và được gán vào entry->DllBase.
### Tìm kiếm các hàm Export
Khi đã có địa chỉ cơ sở của DLL (trong trường hợp này là kernel32.dll) chúng ta sẽ đi tìm các API cần thiết cho chương trình.
Chúng ta có thể tìm các hàm này thông qua EAT. Để làm được điều này thì cần duyệt file PE. Các hàm được DLL xuất bằng hai cách là sử dụng tên hoặc bằng thứ tự. EAT được truy cập thông qua IMAGE_EXPORT_DIRECTORY bên trong IMAGE_DATA_DIRECTORY.

Đầu tiên chúng ta bắt đầu image thông qua IMAGE_DOS_HEADER và trỏ đến đầu của PE. PE header (hay IMAGE_NT_HEADER có ba thành phần chính. Thành phần mà chúng ta quan tâm là OptionalHeader. OptionalHeader có cấu trúc chiếm 224 bytes và trong đó DataDirectory chiếm 128 bytes. DataDirectory sẽ chứa cấu trúc IMAGE_EXPORT_DIRECTORY mà chúng ta cần.
Sau khi có cấu trúc IMAGE_EXPORT_DIRECTORY chúng ta cần truy cập vào các trường trong nó bao gồm AddressOfFunctions, AddressOfNames.
Về cơ bản IMAGE_EXPORT_DIRECTORY sẽ trỏ đến các trường và kiểm tra với hàm cần tìm. Nếu tìm thấy hàm sẽ trả về một con trỏ đến địa chỉ của hàm tương ứng ở index thứ k.
Con trỏ được tính bằng cách cộng giá trị lưu tại phần tử thứ k của mảng được trỏ bởi funcRVA và sau đó cộng với địa chỉ cơ sở của PE.
### Thực hiện Inject DLL
#### Load DLL vào bộ nhớ
Ở bước này các bytes của DLL sẽ được đọc vào bộ nhớ được cấp phát trong bộ nhớ heap của tiến trình.

#### Copy các section của DLL
Sau khi code được inject vào bộ nhớ, chúng ta cần phải tìm không gian bộ nhớ của tiến trình đích. Con trỏ PIMAGE_DOS_HEADER được sử dụng để trỏ tới điểm bắt đầu file PE trong bộ nhớ. Sau đó ta thực hiện lấy kích thước của PE image thông qua SizeOfImage và cấp phát bộ nhớ với _VirtualAlloc().

Đến đây chúng ta sẽ cần lặp qua tất cả các section của file PE và copy các section này vào một vùng nhớ mới được cấp phát trong bộ nhớ. Như đã biết, file PE được tạo bởi nhiều section mà trong đó chứa các kiểu dữ liệu khác nhau như code, data hay resource.
Macro IMAGE_FIRST_SECTION sẽ trả về một con trỏ tới section đầu tiên của file PE, NumberOfSections là trường chứa tổng số section trong file.
Bên trong vòng lặp chúng ta sẽ copy data của mỗi section từ địa chỉ của nó trong file PE tới vùng nhớ mới được cấp phát. Địa chỉ đích (sectionDestination) được tính toán bằng cách cộng virtual address của section với base address trong vùng nhớ mới, và địa chỉ của nguồn (sectionByte) được tính bằng việc cộng raw data offset với base address của file PE ban đầu. Kích thước của dữ liệu copy sẽ được chỉ định thông qua trường SizeOfRawData của section header.
#### Điều chỉnh vị trí và xử lý bảng Import
Bước tiếp theo trong kỹ thuật này là định vị lại PE image và xử lý IAT của image được load vào bộ nhớ. IAT là bảng chứa địa chỉ của các hàm được lấy ra từ các DLL. Khi một image được load vào bộ nhớ và thực thi, IAT được dùng để xử lý địa chỉ của các hàm lấy ra.

Mục đích của hàm này là cập nhật địa chỉ của IAT. Chúng ta cần làm vậy bởi vì khi image biên dịch, IAT chỉ chứa tên của các hàm được import mà không chứa địa chỉ của chúng. Khi image được load vào bộ nhớ IAT phải được cập nhật với địa chỉ chính xác của các hàm được import để image có thể gọi đến nó.
Chúng ta thực hiện duyệt mảng Import Directory trong cấu trúc IMAGE_IMPORT_DESCRIPTOR. Cấu trúc này chứa thông tin về các DLL mà file PE đã import. Tại cấu trúc này có ba trường quan trọng:
• OriginalFirstThunk: Tại đây chứa các offset của tên các hàm được import.
• Name: Chứa tên module (DLL) mà hàm sẽ được import (VD: kernel32.dll, user32.dll,…)
• FirstThunk: Chứa các offset đến địa chỉ thực của các hàm được import trong bộ nhớ.
Mỗi descriptor chứa RVA để trỏ đến mảng ở trong cấu trúc IMAGE_THUNK_DATA. Mỗi entry sẽ đại diện cho thông tin về API được import. Sau khi lấy được tên của DLL (bằng cách trỏ đến trường ‘name’), ta sẽ gọi đến hàm _LoadLibraryA để tải DLL vào trong bộ nhớ. Hàm này sẽ trả về một handle tới DLL và được lưu tại biến library. Khi đã có được handle của DLL, ta cần khởi tạo một con trỏ đến đầu bảng FirstThunk (FT) cho DLL. Bảng này chứa địa chỉ thực của các hàm được import trong bộ nhớ. Ta sẽ tiến hành lặp qua các entry của bảng FT. Với mỗi entry thì ta kiểm tra các entry chỉ định các hàm được import bằng tên hay theo thứ tự. Nó sẽ gọi _GetProcAddress() để tìm kiếm địa chỉ của hàm trong DLL và cập nhật địa chỉ chính xác của các entry tương ứng trong bảng FT. Sau khi lặp xong thì bảng FT sẽ chứa các địa chỉ tương ứng với các hàm được import từ DLL và image lúc này sẽ gọi đến các hàm này trong lúc chạy.
Sau khi xử lý bảng Import Address, chúng ta sẽ tiến hành điều chỉnh địa chỉ của image. Như đã biết, khi một file PE được tạo với linker thì nó phải được biết được vị trí mà nó sẽ được ánh xạ vào trong bộ nhớ. Do đó linker sẽ mã hóa các địa chỉ của code và các mục dữ liệu trong file PE đã biên dịch. Tuy nhiên khi tệp thực thi được tải vào memory, nó có thể được đặt tại một vị trí khác với giả định ban đầu nên sẽ gây nên địa chỉ hardcode sẽ không còn chính xác. Để khắc phục cho vấn đề này, PE file có một section là .reloc để chứa các offset liên quan đến thông tin địa chỉ hardcode. Khi tải file PE, linker sẽ dung thông tin trong .reloc để sửa đổi và cập nhật các địa chỉ của image đã tải để đảm bảo nó trỏ đến đúng vị trí trong memory.

Để thực hiện việc định vị lại địa chỉ, đầu tiên ta khởi tạo một con trỏ để trỏ tới đầu của thư mục reloc, sau đó khởi tạo một biến deltaImageBase chứa khoảng cách giữa địa chỉ của image base khi được load vào trong memory và địa chỉ của image base khi được compile. Tiếp theo chúng ta lấy các mục trong relocation data directory và tính toán RVA của relocation table. Sau đó lặp qua các mục trong relocation table và tính toán địa chỉ để patch dựa vào kiểu relocation và offset (bằng cấu trúc BASE_RELOCATION_ENTRY và BASE_RELOCATION_BLOCK. Nó đọc giá trị ban đầu của địa chỉ cần patch rồi cộng với deltaImageBase để cập nhật địa chỉ sang vị trí mới. Việc này để đảm bảo DLL chạy chính xác tại địa chỉ image base của nó.
### Thực thi DLL
Đến đây là ta đã có thể thực thi được DLL mà ta vừa inject.
